fbpixel
Créer une application BLE pour ESP32 avec React Native

Créer une application BLE pour ESP32 avec React Native

Nous allons voir comment créer une application React Native pour Adnroid permettant la communication BLE (Bluetooth Low Energy) avec un ESP32. Nous utilisons React Native pour développer un terminal BLE sur Android permettant la communication avec un NodeMCU ESP32 ou tout autres appareils compatibles.

Matériel

  • Un ordinateur avec installation de React Native et Node.js
  • Un appareil Android avec BLE
  • Un câble USB pour relier l’ordinateur à l’appareil
  • Un appareil BLE (ESP32)

Code de gestion du BLE pour ESP32

Pour tester l’application React Native, nous allons utiliser le code de gestion du BLE pour ESP32.

/*
    Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleWrite.cpp
    Ported to Arduino ESP32 by Evandro Copercini
*/

#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>

// See the following for generating UUIDs:
// https://www.uuidgenerator.net/

#define SERVICE_UUID        "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"

BLECharacteristic *pCharacteristic = NULL;

std::string msg;

class MyCallbacks: public BLECharacteristicCallbacks {
    void onWrite(BLECharacteristic *pCharacteristic) {
      std::string value = pCharacteristic->getValue();

      if (value.length() > 0) {
        Serial.println("*********");
        Serial.print("New value: ");
        for (int i = 0; i < value.length(); i++)
          Serial.print(value[i]);

        Serial.println();
        Serial.println("*********");
      }
    }
};

class MyServerCallbacks: public BLEServerCallbacks {
  void onConnect(BLEServer* pServer) {
    Serial.println("Client connected");
  }
  void onDisconnect(BLEServer* pServer) {
    Serial.println("Client disconnected");
    BLEDevice::startAdvertising(); // needed for reconnection
  }
};

void setup() {
  Serial.begin(115200);

  Serial.println("1- Download and install an BLE Terminal Free");
  Serial.println("2- Scan for BLE devices in the app");
  Serial.println("3- Connect to ESP32BLE");
  Serial.println("4- Go to CUSTOM CHARACTERISTIC in CUSTOM SERVICE and write something");

  BLEDevice::init("ESP32BLE");
  BLEServer *pServer = BLEDevice::createServer();
  pServer->setCallbacks(new MyServerCallbacks());
  
  BLEService *pService = pServer->createService(SERVICE_UUID);

  pCharacteristic = pService->createCharacteristic(
                                         CHARACTERISTIC_UUID,
                                         BLECharacteristic::PROPERTY_READ |
                                         BLECharacteristic::PROPERTY_WRITE
                                       );

  pCharacteristic->setCallbacks(new MyCallbacks());

  pCharacteristic->setValue("Hello World");
  pService->start();

  BLEAdvertising *pAdvertising = pServer->getAdvertising();
  pAdvertising->addServiceUUID(SERVICE_UUID);
  pAdvertising->setScanResponse(true);
  pAdvertising->setMinPreferred(0x06);  // functions that help with iPhone connections issue
  pAdvertising->setMinPreferred(0x12);
  pAdvertising->start();

  Serial.print("Server address:");
  Serial.println(BLEDevice::getAddress().toString().c_str());
}

void loop() {
  readSerialPort();

  //Send data to slave
  if(msg!=""){
    pCharacteristic->setValue(msg);
    msg="";
  }
  
  delay(2000);
}

void readSerialPort(){
 while (Serial.available()) {
   delay(10);  
   if (Serial.available() >0) {
     char c = Serial.read();  //gets one byte from serial buffer
     msg += c; //add to String
   }
 }
 Serial.flush(); //clean buffer
}

Nous rajoutons la fonction BLEServerCallbacks à la gestion du Serveur BLE pour détecter la déconnexion et démarrer l’advertising pour pouvoir reconnecter l’ESP32

  pServer->setCallbacks(new MyServerCallbacks());

Application React Native pour la gestion du BLE

Pour gérer la communciation BLE (Bluetooth Low Energy) sur l’appareil Android, nous utilisons la librairie react-native-ble-manager

npm install react-native-ble-manager --save

Pour mettre en place le projet de l’application, suivez le tutoriel précédent.

Dans le fichier App.tsx, pour utiliser la bibliothèque nous l’importons à l’aide de la commande

import BleManager from 'react-native-ble-manager';

Nous créons un composant fonctionnel qui contiendra les éléments nous permettant de gérer la communication BLE

let serviceid="4fafc201-1fb5-459e-8fcc-c5c9c331914b";
let caracid="beb5483e-36e1-4688-b7f5-ea07361b26a8";

const BluetoothBLETerminal = () =>  {
 const [devices, setDevices] = useState<any[]>([]);
 const [paired, setPaired] = useState<any[]>([]);
 const [selectedDevice, setSelectedDevice] = useState<Peripheral>();
 const [messageToSend, setMessageToSend] = useState("");
 const [receivedMessage, setReceivedMessage] = useState("");
 const [isConnected, setIsConnected] = useState(false);
 const [intervalId, setIntervalId] = useState<NodeJS.Timer>();
 const [isScanning, setIsScanning] = useState(false);

N.B.: il est possible de créer un composant qui dérive de ReactNative.Components

Gestion des permissions

Pour pouvoir découvrir et se connecter à des appareils Bluetooth, il faut 3 permissions au minimum:

  • BLUETOOTH_SCAN
  • BLUETOOTH_CONNECT
  • ACCESS_FINE_LOCATION

N.B.: ces permissions dépendent de la version et de l’OS utilisé

Voici les balises à ajouter dans le fichier AndroidManifest.xml

  <uses-permission android:name="android.permission.INTERNET" />
  <uses-permission android:name="android.permission.BLUETOOTH" />
  <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
  <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
  <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
  <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
  <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

Dans le fichier App.tsx, nous créons la fonction requestBluetoothPermission()

    if (Platform.OS === 'android' && Platform.Version >= 23) {
  
        PermissionsAndroid.requestMultiple([
          PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN,
          PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT,
          PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
        ]).then(result => {
          if (
            (result['android.permission.BLUETOOTH_SCAN'] &&
            result['android.permission.BLUETOOTH_CONNECT'] &&
            result['android.permission.ACCESS_FINE_LOCATION'] === 'granted')
            ||
            (result['android.permission.BLUETOOTH_SCAN'] &&
            result['android.permission.BLUETOOTH_CONNECT'] &&
            result['android.permission.ACCESS_FINE_LOCATION'] === 'never_ask_again')
          ) {
            console.log('User accepted');
          } else {
            console.log('User refused');        }
        });
    }

Fonction de gestion du BLE

Les fonctions permettant la gestion du Bluetooth LE sont les suivantes:

  • découvrir des appareils bluetooth startDeviceDiscovery() (j’utilise les appareils apairés)
  • se connecter à l’appareil connectToDevice()
  • se déconnecter disconnectFromDevice()
  • envoyer des messages sendMessage()
  • lire les messages provenant de la communication readData()

N.B.: Dans cet exemple, nous écrivons et lisons sur la même caractéristique. Nous lisons donc la valeur enregistré avec un appui bouton

const checkBluetoothEnabled = async () => {
   try {
         // turn on bluetooth if it is not on
   BleManager.enableBluetooth().then(() => {
     console.log('Bluetooth is turned on!');
   });
     
   } catch (error) {
     console.error('BLE is not available on this device.');
   }
 }
 
 const startScan = () => {
  if (!isScanning) {
    BleManager.scan([], 5, true)
      .then(() => {
        console.log('Scanning...');
        setIsScanning(true);
      })
      .catch(error => {
        console.error(error);
      });
  }
};

 const startDeviceDiscovery = async () => {

  BleManager.getBondedPeripherals().then((bondedPeripheralsArray) => {
    // Each peripheral in returned array will have id and name properties
    console.log("Bonded peripherals: " + bondedPeripheralsArray.length);
    setPaired(bondedPeripheralsArray);
  });

  /*BleManager.getDiscoveredPeripherals().then((peripheralsArray) => {
    // Success code
    console.log("Discovered peripherals: " + peripheralsArray.length);
  });*/
 }

 const connectToDevice = async (device: Peripheral) => {
 BleManager.connect(device.id)
     .then(() => {
     // Success code
     console.log("Connected");
     setSelectedDevice(device);
     setIsConnected(true);
     BleManager.retrieveServices(device.id).then(
       (deviceInfo) => {
       // Success code
       console.log("Device info:", deviceInfo);
       }
     );


     })
     .catch((error) => {
     // Failure code
     console.log(error);
     });
 }


const sendMessage = async () => {
 if(selectedDevice && isConnected){
   try {

    const buffer = Buffer.from(messageToSend);
    BleManager.write(
      selectedDevice.id,
      serviceid,
      caracid,
      buffer.toJSON().data
    ).then(() => {
      // Success code
      console.log("Write: " + buffer.toJSON().data);
    })
    .catch((error) => {
      // Failure code
      console.log(error);
    });
     
   } catch (error) {
     console.error('Error sending message:', error);
   }
 }
}


const readData = async () => {  
 if (selectedDevice && isConnected) {
    BleManager.read(
      selectedDevice.id,
      serviceid,
      caracid
    )
      .then((readData) => {
        // Success code
        console.log("Read: " + readData);
        const message = Buffer.from(readData);
        //const sensorData = buffer.readUInt8(1, true);
        if(receivedMessage.length>300){
          setReceivedMessage(""); //reset received message if length higher than 300
        }
        setReceivedMessage(receivedMessage => receivedMessage + message +"\n" );
        console.log("receivedMessage length",receivedMessage.length)
      })
      .catch((error) => {
        // Failure code
        console.log("Error reading message:",error);
      });
 }
}

 // disconnect from device
 const disconnectFromDevice = (device: Peripheral) => {
   BleManager.disconnect(device.id)
   .then(() => {
        setSelectedDevice(undefined);
        setIsConnected(false);
        setReceivedMessage("");
        clearInterval(intervalId);
        console.log("Disconnected from device");
   })
   .catch((error) => {
     // Failure code
     console.log("Error disconnecting:",error);
   });
 };

La fonction de rendu de l’écran

Pour l’affichage, nous choisissons de tous mettre sur un même écran. Il y aura :

  • Un titre
  • La liste des appareils qui n’apparait que si on n’est pas connecté (!isConnected &&)
  • Un encart type terminal de communication qui n’apparait que si on est connecté (selectedDevice && isConnected &&)
    • TextInput pour écrire le message à envoyer messageToSend
    • Un bouton d’envoi
    • Un bouton de lecture
    • Une zone de texte pour afficher receivedMessage
    • Un bouton de déconnexion
    return (
      <View>
      <Text
            style={{
              fontSize: 30,
              textAlign: 'center',
              borderBottomWidth: 1,
            }}>
            AC Bluetooth Terminal
          </Text>
        <ScrollView>

          {!isConnected && (
          <>
          {/*
          <Text>Available Devices:</Text>
          {devices.map((device) => (
            <Button
              key={device.id}
              title={device.name || 'Unnamed Device'}
              onPress={() => this.connectToDevice(device)}
            />
          ))}
          */}
          <Text>Paired Devices:</Text>
          {paired.map((pair: BluetoothDevice) => (
                      <View
                      style={{
                        flexDirection: 'row',
                        justifyContent: 'space-between',
                        marginBottom: 2,
                      }}>
                      <View style={styles.deviceItem}>
                        <Text style={styles.deviceName}>{pair.name}</Text>
                        <Text style={styles.deviceInfo}>{pair.id}</Text>
                      </View>
                      <TouchableOpacity
                        onPress={() =>
                          isConnected
                            ?  disconnect()
                            :  connectToDevice(pair)
                        }
                        style={styles.deviceButton}>
                        <Text
                          style={[
                            styles.scanButtonText,
                            {fontWeight: 'bold', fontSize: 12},
                          ]}>
                          {isConnected ? 'Disconnect' : 'Connect'}
                        </Text>
                      </TouchableOpacity>
                    </View>
          ))}
          </>  
          )}
          {selectedDevice && isConnected && (
            <>
              <View
                      style={{
                        flexDirection: 'row',
                        justifyContent: 'space-between',
                        margin: 5,
                      }}>
                      <View style={styles.deviceItem}>
                        <Text style={styles.deviceName}>{selectedDevice.name}</Text>
                        <Text style={styles.deviceInfo}>{selectedDevice.id}</Text>
                      </View>
                      <TouchableOpacity
                        onPress={() =>
                          isConnected
                            ?  disconnect()
                            :  connectToDevice(selectedDevice)
                        }
                        style={styles.deviceButton}>
                        <Text
                          style={styles.scanButtonText}>
                          {isConnected ? 'Disconnect' : 'Connect'}
                        </Text>
                      </TouchableOpacity>
                    </View>

          <View
              style={{
                flexDirection: 'row',
                justifyContent: 'space-between',
                margin: 5,
              }}>        
              <TextInput
                style={{
                  backgroundColor: '#888888',
                  margin: 2,
                  borderRadius: 15,
                  flex:3,
                  }}
                placeholder="Enter a message"
                value={messageToSend}
                onChangeText={(text) => setMessageToSend(text )}
              />
              <TouchableOpacity
                        onPress={() => sendMessage()
                        }
                        style={[styles.sendButton]}>
                        <Text
                          style={[
                            styles.scanButtonText,
                          ]}>
                          SEND
                        </Text>
                      </TouchableOpacity>
        </View>
              <Text>Received Message:</Text>
              <TextInput
              editable = {false}
              multiline
              numberOfLines={20}
              maxLength={100}
                style={{
                  backgroundColor: '#333333',
                  margin: 10,
                  borderRadius: 2,
                  borderWidth: 1,
                  borderColor: '#EEEEEE',
                  textAlignVertical: 'top',
                  }} >
                    {receivedMessage}
              </TextInput>
              
            </>
          )}
        </ScrollView>
      </View>
    );

Résultat

Comme l’appairage n’est pas gérer par l’application, il faut appairer l’ESP32 avant l’utilisation de l’application. Une fois le code chargé sur l’ESP32, vous pouvez lancer l’application sur le téléphone à l’aide de la commande

npx react-native start

Code complet de l’application React Native

/**
* Sample React Native App
* https://github.com/facebook/react-native
*
* @format
* https://github.com/innoveit/react-native-ble-manager
* https://blog.logrocket.com/using-react-native-ble-manager-mobile-app/
*/



import React, {useState, useEffect} from 'react';
import {   
  StyleSheet,
  Dimensions,
  View, 
  ScrollView, 
  Text,
  TextInput,
  PermissionsAndroid,
  TouchableOpacity,
  Platform} from 'react-native';
import BleManager,{Peripheral} from 'react-native-ble-manager';
import { Buffer } from 'buffer';

let serviceid="4fafc201-1fb5-459e-8fcc-c5c9c331914b";
let caracid="beb5483e-36e1-4688-b7f5-ea07361b26a8";

const BluetoothBLETerminal = () =>  {
 const [devices, setDevices] = useState<any[]>([]);
 const [paired, setPaired] = useState<any[]>([]);
 const [selectedDevice, setSelectedDevice] = useState<Peripheral>();
 const [messageToSend, setMessageToSend] = useState("");
 const [receivedMessage, setReceivedMessage] = useState("");
 const [isConnected, setIsConnected] = useState(false);
 const [intervalId, setIntervalId] = useState<NodeJS.Timer>();
 const [isScanning, setIsScanning] = useState(false);

 const checkBluetoothEnabled = async () => {
   try {
         // turn on bluetooth if it is not on
   BleManager.enableBluetooth().then(() => {
     console.log('Bluetooth is turned on!');
   });
     
   } catch (error) {
     console.error('BLE is not available on this device.');
   }
 }
 
 const startScan = () => {
  if (!isScanning) {
    BleManager.scan([], 5, true)
      .then(() => {
        console.log('Scanning...');
        setIsScanning(true);
      })
      .catch(error => {
        console.error(error);
      });
  }
};

 const startDeviceDiscovery = async () => {

  BleManager.getBondedPeripherals().then((bondedPeripheralsArray) => {
    // Each peripheral in returned array will have id and name properties
    console.log("Bonded peripherals: " + bondedPeripheralsArray.length);
    setPaired(bondedPeripheralsArray);
  });

  /*BleManager.getDiscoveredPeripherals().then((peripheralsArray) => {
    // Success code
    console.log("Discovered peripherals: " + peripheralsArray.length);
  });*/
 }

 const connectToDevice = async (device: Peripheral) => {
 BleManager.connect(device.id)
     .then(() => {
     // Success code
     console.log("Connected");
     setSelectedDevice(device);
     setIsConnected(true);
     BleManager.retrieveServices(device.id).then(
       (deviceInfo) => {
       // Success code
       console.log("Device info:", deviceInfo);
       }
     );


     })
     .catch((error) => {
     // Failure code
     console.log(error);
     });
 }


const sendMessage = async () => {
 if(selectedDevice && isConnected){
   try {

    const buffer = Buffer.from(messageToSend);
    BleManager.write(
      selectedDevice.id,
      serviceid,
      caracid,
      buffer.toJSON().data
    ).then(() => {
      // Success code
      console.log("Write: " + buffer.toJSON().data);
    })
    .catch((error) => {
      // Failure code
      console.log(error);
    });
     
   } catch (error) {
     console.error('Error sending message:', error);
   }
 }
}


const readData = async () => {  
 if (selectedDevice && isConnected) {
    BleManager.read(
      selectedDevice.id,
      serviceid,
      caracid
    )
      .then((readData) => {
        // Success code
        console.log("Read: " + readData);
        const message = Buffer.from(readData);
        //const sensorData = buffer.readUInt8(1, true);
        if(receivedMessage.length>300){
          setReceivedMessage("");
        }
        setReceivedMessage(receivedMessage => receivedMessage + message +"\n" );
        console.log("receivedMessage length",receivedMessage.length)
      })
      .catch((error) => {
        // Failure code
        console.log("Error reading message:",error);
      });
 }
}

/*useEffect(() => {
 let intervalId: string | number | NodeJS.Timer | undefined;
 if (selectedDevice && isConnected) {
   intervalId = setInterval(() => readData(), 100);
   setIntervalId(intervalId);
 }
 return () => {
   clearInterval(intervalId);
 };
}, [isConnected,selectedDevice]);*/

 // disconnect from device
 const disconnectFromDevice = (device: Peripheral) => {
   BleManager.disconnect(device.id)
   .then(() => {
        setSelectedDevice(undefined);
        setIsConnected(false);
        setReceivedMessage("");
        clearInterval(intervalId);
        console.log("Disconnected from device");
   })
   .catch((error) => {
     // Failure code
     console.log("Error disconnecting:",error);
   });
   
   /*BleManager.removeBond(peripheral.id)
     .then(() => {
       peripheral.connected = false;
       peripherals.set(peripheral.id, peripheral);
       setConnectedDevices(Array.from(peripherals.values()));
       setDiscoveredDevices(Array.from(peripherals.values()));
       Alert.alert(`Disconnected from ${peripheral.name}`);
     })
     .catch(() => {
       console.log('fail to remove the bond');
     });*/



 };
 
 
 useEffect(() => {

    checkBluetoothEnabled();

    if (Platform.OS === 'android' && Platform.Version >= 23) {
  
        PermissionsAndroid.requestMultiple([
          PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN,
          PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT,
          PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
        ]).then(result => {
          if (
            (result['android.permission.BLUETOOTH_SCAN'] &&
            result['android.permission.BLUETOOTH_CONNECT'] &&
            result['android.permission.ACCESS_FINE_LOCATION'] === 'granted')
            ||
            (result['android.permission.BLUETOOTH_SCAN'] &&
            result['android.permission.BLUETOOTH_CONNECT'] &&
            result['android.permission.ACCESS_FINE_LOCATION'] === 'never_ask_again')
          ) {
            console.log('User accepted');
          } else {
            console.log('User refused');        }
        });

    }

    BleManager.start({showAlert: false}).then(() => {
      console.log('BleManager initialized');
      startDeviceDiscovery();
    }).catch((error) => {
      // Failure code
      console.log("Error requesting permission:",error);
    });
   
BleManager.checkState().then((state) =>
   console.log(`current BLE state = '${state}'.`)
 );

 BleManager.getConnectedPeripherals([]).then((peripheralsArray) => {
   // Success code
   console.log("Connected peripherals: " + peripheralsArray.length);
 });

 BleManager.getBondedPeripherals().then((bondedPeripheralsArray) => {
   // Each peripheral in returned array will have id and name properties
   console.log("Bonded peripherals: " + bondedPeripheralsArray.length);
   //setBoundedDevices(bondedPeripheralsArray);
 });

 BleManager.getDiscoveredPeripherals().then((peripheralsArray) => {
   // Success code
   console.log("Discovered peripherals: " + peripheralsArray.length);
 });

 /*let stopDiscoverListener = BleManagerEmitter.addListener(
   'BleManagerDiscoverPeripheral',
   peripheral => {
     peripherals.set(peripheral.id, peripheral);
   },
 );*/

 /*let stopConnectListener = BleManagerEmitter.addListener(
   'BleManagerConnectPeripheral',
   peripheral => {
     console.log('BleManagerConnectPeripheral:', peripheral);
     peripherals.set(peripheral.id, peripheral);
     setConnectedDevices(Array.from(peripherals.values()));
   },
 );*/

 /*let stopScanListener = BleManagerEmitter.addListener(
   'BleManagerStopScan',
   () => {
     setIsScanning(false);
     console.log('scan stopped');
     BleManager.getDiscoveredPeripherals().then((peripheralsArray) => {
       // Success code
       console.log("Discovered peripherals: " + peripheralsArray.length);
       for (let i = 0; i < peripheralsArray.length; i++) {
         let peripheral = peripheralsArray[i];
         console.log("item:", peripheral);
         //peripheral.connected = true;
         peripherals.set(peripheral.id, peripheral);
         setDiscoveredDevices(peripheralsArray);
       }

       
     });
   },
 );*/

 return () => {
   /*stopDiscoverListener.remove();
   stopConnectListener.remove();
   stopScanListener.remove();*/
 };
 }, [])


   return (
     <View style={[styles.mainBody]}>
     <Text
           style={{
             fontSize: 30,
             textAlign: 'center',
             borderBottomWidth: 1,
           }}>
           AC BLE Terminal
         </Text>
       <ScrollView>

         {!isConnected && (
         <>

        <TouchableOpacity
                       onPress={() => startDeviceDiscovery()
                       }
                       style={[styles.deviceButton]}>
                       <Text
                         style={[
                           styles.scanButtonText,
                         ]}>
                         SCAN
                       </Text>
              </TouchableOpacity>

         {/*
         <Text>Available Devices:</Text>
         {devices.map((device) => (
           <Button
             key={device.id}
             title={device.name || 'Unnamed Device'}
             onPress={() => this.connectToDevice(device)}
           />
         ))}
         */}
         <Text>Paired Devices:</Text>
         {paired.map((pair,i) => (
                     <View key={i}
                     style={{
                       flexDirection: 'row',
                       justifyContent: 'space-between',
                       marginBottom: 2,
                     }}>
                     <View style={styles.deviceItem}>
                       <Text style={styles.deviceName}>{pair.name}</Text>
                       <Text style={styles.deviceInfo}>{pair.id}, rssi: {pair.rssi}</Text>
                     </View>
                     <TouchableOpacity
                       onPress={() =>
                         isConnected
                           ?  disconnectFromDevice(pair)
                           :  connectToDevice(pair)
                       }
                       style={styles.deviceButton}>
                       <Text
                         style={[
                           styles.scanButtonText,
                           {fontWeight: 'bold', fontSize: 12},
                         ]}>
                         {isConnected ? 'Disconnect' : 'Connect'}
                       </Text>
                     </TouchableOpacity>
                   </View>
         ))}
         </>  
         )}
         {selectedDevice && isConnected && (
           <>
             <View
                     style={{
                       flexDirection: 'row',
                       justifyContent: 'space-between',
                       margin: 5,
                     }}>
                     <View style={styles.deviceItem}>
                       <Text style={styles.deviceName}>{selectedDevice.name}</Text>
                       <Text style={styles.deviceInfo}>{selectedDevice.id}, rssi: {selectedDevice.rssi}</Text>
                     </View>
                     <TouchableOpacity
                       onPress={() =>
                         isConnected
                           ?  disconnectFromDevice(selectedDevice)
                           :  connectToDevice(selectedDevice)
                       }
                       style={styles.deviceButton}>
                       <Text
                         style={styles.scanButtonText}>
                         {isConnected ? 'Disconnect' : 'Connect'}
                       </Text>
                     </TouchableOpacity>
                   </View>

         <View
             style={{
               flexDirection: 'row',
               justifyContent: 'space-between',
               margin: 5,
             }}>        
             <TextInput
               style={{
                 backgroundColor: '#888888',
                 margin: 2,
                 borderRadius: 15,
                 flex:3,
                 }}
               placeholder="Enter a message"
               value={messageToSend}
               onChangeText={(text) => setMessageToSend(text)}
             />
             <TouchableOpacity
                       onPress={() => sendMessage()
                       }
                       style={[styles.sendButton]}>
                       <Text
                         style={[
                           styles.scanButtonText,
                         ]}>
                         SEND
                       </Text>
                     </TouchableOpacity>
       </View>
       <View
             style={{
               flexDirection: 'row',
               justifyContent: 'space-between',
               margin: 5,
             }}>
             <Text style={{textAlignVertical: 'center'}}>Received Message:</Text>
             <TouchableOpacity
                       onPress={() => readData()
                       }
                       style={[styles.deviceButton]}>
                       <Text
                         style={[
                           styles.scanButtonText,
                         ]}>
                         READ
                       </Text>
              </TouchableOpacity>
        </View>
             <TextInput
             editable = {false}
             multiline
             numberOfLines={20}
             maxLength={300}
               style={{
                 backgroundColor: '#333333',
                 margin: 10,
                 borderRadius: 2,
                 borderWidth: 1,
                 borderColor: '#EEEEEE',
                 textAlignVertical: 'top',
                 }} >
                   {receivedMessage}
             </TextInput>
             
           </>
         )}
       </ScrollView>
     </View>
   );

};//end of component

//https://medium.com/supercharges-mobile-product-guide/reactive-styles-in-react-native-79a41fbdc404
export const theme = {
  smallPhone: 0,
  phone: 290,
  tablet: 750,
  }

const windowHeight = Dimensions.get('window').height;
const styles = StyleSheet.create({
 mainBody: {
   flex: 1,
   justifyContent: 'center',
   height: windowHeight,

   ...Platform.select ({
    ios: {
      fontFamily: "Arial",
    },
    
    android: {
      fontFamily: "Roboto",

    },
  }),
 },

 scanButtonText: {
   color: 'white',
   fontWeight: 'bold',
   fontSize: 12,
   textAlign: 'center',
 },
 noDevicesText: {
   textAlign: 'center',
   marginTop: 10,
   fontStyle: 'italic',
 },
 deviceItem: {
   marginBottom: 2,
 },
 deviceName: {
   fontSize: 14,
   fontWeight: 'bold',
 },
 deviceInfo: {
   fontSize: 8,
 },
 deviceButton: {
   backgroundColor: '#2196F3',
   padding: 10,
   borderRadius: 10,
   margin: 2,
   paddingHorizontal: 20,
 },
 sendButton: {
  backgroundColor: '#2196F3',
  padding: 15,
  borderRadius: 15,
  margin: 2,
  paddingHorizontal: 20,
 },
});

export default BluetoothBLETerminal;

Sources

Créer une application BLE pour ESP32 avec React Native

Créer une application Bluetooth pour ESP32 avec React Native

Nous allons voir comment créer une application React Native permettant la communication Bluetooth entre un appareil Android et un ESP32. Nous utilisons React Native pour développer un terminal Bluetooth sur Android permettant la communication avec un NodeMCU ESP32. Le NodeMCU permet de tester notre application avec un objet connecté mais l’application peut fonctionner avec n’importe quel appareil Bluetooth.

Matériel

  • Un ordinateur avec installation de React Native et Node.js
  • Un appareil Android avec Bluetooth
  • Un câble USB pour relier l’ordinateur à l’appareil
  • Un appareil Bluetooth (ESP32)

Code de gestion du Bluetooth pour ESP32

Pour tester l’application React Native, nous allons utiliser le code de gestion du Bluetooth pour ESP32.

#include "BluetoothSerial.h"

#if !defined(CONFIG_BT_ENABLED) || !defined(CONFIG_BLUEDROID_ENABLED)
#error Bluetooth is not enabled! Please run `make menuconfig` to and enable it
#endif

BluetoothSerial SerialBT;

void callback(esp_spp_cb_event_t event, esp_spp_cb_param_t *param) {
  if (event == ESP_SPP_SRV_OPEN_EVT) {
    Serial.println("Client Connected");
  }

  if (event == ESP_SPP_CLOSE_EVT ) {
    Serial.println("Client disconnected");
    //SerialBT.flush();
    //SerialBT.disconnect();
    //SerialBT.end();
    //SerialBT.begin("ESP32BT");
    ESP.restart(); // needed to be able to reconnect
  }
}

void setup() {
  Serial.begin(115200);

  SerialBT.register_callback(callback);
  SerialBT.begin("ESP32BT"); //Bluetooth device name
  Serial.println("The device started, now you can pair it with bluetooth!");
}

String msg = "";
void loop() {
  /*if (Serial.available()) {
    SerialBT.write(Serial.read());
    }*/

  readSerialPort();
  //Send data to slave
  if (msg != "") {
    Serial.println(msg);
    SerialBT.println(msg);
    msg = "";
  }

  if (SerialBT.available()) {
    Serial.write(SerialBT.read());
  }
  delay(20);
}

void readSerialPort() {
  while (Serial.available()) {
    delay(10);
    if (Serial.available() > 0) {
      char c = Serial.read();  //gets one byte from serial buffer
      msg += c; //add to String
    }
  }
  Serial.flush(); //clean buffer
}

Nous rajoutons la fonction callback à la gestion du Bluetooth pour détecter la déconnexion et redémarrer l’ESP32

SerialBT.register_callback(callback);

N.B.: le reset de l’ESP32 est nécessaire à la reconnexion du Bluetooth car, visiblement, le BluetoothSocket ne se ferme pas à la deconnexion

Application React Native pour la gestion du Bluetooth

Pour gérer la communciation Bluetooth (classic) sur l’appareil Android, nous utilisons la librairie react-native-bluetooth-classic

npm install react-native-bluetooth-classic --save

Pour mettre en place le projet de l’application, suivez le tutoriel précédent.

Dans le fichier App.tsx, pour utiliser la bibliothèque nous l’importons à l’aide de la commande

import RNBluetoothClassic, {BluetoothDevice,} from 'react-native-bluetooth-classic';

Nous créons un composant fonctionnel qui contiendra les éléments nous permettant de gérer la communication Bluetooth

const BluetoothClassicTerminal = () =>  {
  const [devices, setDevices] = useState<any[]>([]);
  const [paired, setPaired] = useState<any[]>([]);
  const [selectedDevice, setSelectedDevice] = useState<BluetoothDevice>();
  const [messageToSend, setMessageToSend] = useState("");
  const [receivedMessage, setReceivedMessage] = useState("");
  const [isConnected, setIsConnected] = useState(false);
  const [intervalId, setIntervalId] = useState<NodeJS.Timer>();

N.B.: il est possible de créer un composant qui dérive de ReactNative.Components

Gestion des permissions

Pour pouvoir découvrir et se connecter à des appareils Bluetooth, il faut 3 permissions au minimum:

  • BLUETOOTH_SCAN
  • BLUETOOTH_CONNECT
  • ACCESS_FINE_LOCATION

N.B.: ces permissions dépendent de la version et de l’OS utilisé

Voici les balises à ajouter dans le fichier AndroidManifest.xml

  <uses-permission android:name="android.permission.INTERNET" />
  <uses-permission android:name="android.permission.BLUETOOTH" />
  <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
  <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
  <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
  <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
  <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

Dans le fichier App.tsx, nous créons la fonction requestBluetoothPermission()

async function requestBluetoothPermission(){
      try {
        const grantedScan = await PermissionsAndroid.request(
          PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN,
          {
            title: 'Bluetooth Scan Permission',
            message: 'This app needs Bluetooth Scan permission to discover devices.',
            buttonPositive: 'OK',
            buttonNegative: 'Cancel',
          }
        );
  
        const grantedConnect = await PermissionsAndroid.request(
          PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT,
          {
            title: 'Bluetooth Connect Permission',
            message: 'This app needs Bluetooth Connect permission to connect to devices.',
            buttonPositive: 'OK',
            buttonNegative: 'Cancel',
          }
        );
      
        const grantedLocation = await PermissionsAndroid.request(
          PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
          {
            title: 'Fine Location Permission',
            message: 'This app needs to know location of device.',
            buttonPositive: 'OK',
            buttonNegative: 'Cancel',
          }
        );
  
        if (
          grantedScan === PermissionsAndroid.RESULTS.GRANTED &&
          grantedConnect === PermissionsAndroid.RESULTS.GRANTED &&
          grantedLocation === PermissionsAndroid.RESULTS.GRANTED
        ) {
          console.log('Bluetooth permissions granted');
          // Vous pouvez maintenant commencer la découverte et la connexion Bluetooth ici.
        } else {
          console.log('Bluetooth permissions denied');
        }
      
      
      } catch (err) {
        console.warn(err);
      }
    }

Fonction de gestion du Bluetooth

Les fonctions permettant la gestion du Bluetooth sont les suivantes:

  • découvrir des appareils bluetooth startDeviceDiscovery() (j’utilise les appareils apairés)
  • se connecter à l’appareil connectToDevice()
  • se déconnecter disconnect()
  • envoyer des messages sendMessage()
  • lire les messages provenant de la communication readData()

N.B.: la documentation de la librairie mentionne l’utilisation d’un listener onDataReceived que je n’ai pas réussi à utiliser. J’introduis donc la fonction readData et un Interval pour récupérer les données.

const checkBluetoothEnabled = async () => {
    try {
      const enabled = await RNBluetoothClassic.isBluetoothEnabled();
      if (!enabled) {
        await RNBluetoothClassic.requestBluetoothEnabled();
      }
      
    } catch (error) {
      console.error('Bluetooth Classic is not available on this device.');
    }
  }
  
  const startDeviceDiscovery = async () => {
    console.log("searching for devices...");
    try {
      const paired = await RNBluetoothClassic.getBondedDevices();
      console.log("Bonded peripherals: " + paired.length);
      setPaired(paired);
    } catch (error) {
      console.error('Error bonded devices:', error);
    }
  }

  const connectToDevice = async (device: BluetoothDevice) => {
    try {
      console.log("Connecting to device");
      let connection = await device.isConnected();
      if (!connection) {
        console.log("Connecting to device");
        await device.connect({
          connectorType: "rfcomm",
          DELIMITER: "\n",
          DEVICE_CHARSET: Platform.OS === "ios" ? 1536 : "utf-8",
        });
      }
      setSelectedDevice(device);
      setIsConnected(true);
      console.log("is connected : ",isConnected);
      //device.onDataReceived((data) => this.readData());
      //const intervalId = setInterval(() => {readData();}, 100);
      //setIntervalId(intervalId);

      
    } catch (error) {
      console.error('Error connecting to device:', error);
    }
  }


const sendMessage = async () => {
  if(selectedDevice && isConnected){
    console.log("isConnected in message",isConnected);
    try {
      await selectedDevice.write(messageToSend);
      
    } catch (error) {
      console.error('Error sending message:', error);
    }
  }
}

const readData = async () => {  
  if (selectedDevice && isConnected) {
    try {
        let message = await selectedDevice.read();
        if(message){
          message = message.trim();
          if (message !== "" && message !== " "){
            if(receivedMessage.length>300){
              setReceivedMessage("");
            }
            setReceivedMessage(receivedMessage => receivedMessage + message +"\n" );
          }
        }

    } catch (error) {
      console.error('Error reading message:', error);
    }
  }
}

useEffect(() => {
  let intervalId: string | number | NodeJS.Timer | undefined;
  if (selectedDevice && isConnected) {
    intervalId = setInterval(() => readData(), 100);
  }
  return () => {
    clearInterval(intervalId);
  };
}, [isConnected,selectedDevice]);

const disconnect = () => {
  //need to reset esp32 at disconnect
  if(selectedDevice && isConnected){
    try {
      clearInterval(intervalId);
      setIntervalId(undefined);
      
      selectedDevice.clear().then( () => {
        console.log("BT buffer cleared");
      });

      selectedDevice.disconnect().then( () => {
        setSelectedDevice(undefined);
        setIsConnected(false);
        setReceivedMessage("");
        console.log("Disconnected from device");
      });

    } catch (error) {
      console.error('Error disconnecting:', error);
    }
  }
}

La fonction de rendu de l’écran

Pour l’affichage, nous choisissons de tous mettre sur un même écran. Il y aura :

  • Un titre
  • La liste des appareils qui n’apparait que si on n’est pas connecté (!isConnected &&)
  • Un encart type terminal de communication qui n’apparait que si on est connecté (selectedDevice && isConnected &&)
    • TextInput pour écrire le message à envoyer messageToSend
    • Un bouton d’envoi
    • Une zone de texte pour afficher receivedMessage
    • Un bouton de déconnexion
    return (
      <View>
      <Text
            style={{
              fontSize: 30,
              textAlign: 'center',
              borderBottomWidth: 1,
            }}>
            AC Bluetooth Terminal
          </Text>
        <ScrollView>

          {!isConnected && (
          <>
          {/*
          <Text>Available Devices:</Text>
          {devices.map((device) => (
            <Button
              key={device.id}
              title={device.name || 'Unnamed Device'}
              onPress={() => this.connectToDevice(device)}
            />
          ))}
          */}
          <Text>Paired Devices:</Text>
          {paired.map((pair: BluetoothDevice,i) => (
                      <View  key={i}
                      style={{
                        flexDirection: 'row',
                        justifyContent: 'space-between',
                        marginBottom: 2,
                      }}>
                      <View style={styles.deviceItem}>
                        <Text style={styles.deviceName}>{pair.name}</Text>
                        <Text style={styles.deviceInfo}>{pair.id}</Text>
                      </View>
                      <TouchableOpacity
                        onPress={() =>
                          isConnected
                            ?  disconnect()
                            :  connectToDevice(pair)
                        }
                        style={styles.deviceButton}>
                        <Text
                          style={[
                            styles.scanButtonText,
                            {fontWeight: 'bold', fontSize: 12},
                          ]}>
                          {isConnected ? 'Disconnect' : 'Connect'}
                        </Text>
                      </TouchableOpacity>
                    </View>
          ))}
          </>  
          )}
          {selectedDevice && isConnected && (
            <>
              <View
                      style={{
                        flexDirection: 'row',
                        justifyContent: 'space-between',
                        margin: 5,
                      }}>
                      <View style={styles.deviceItem}>
                        <Text style={styles.deviceName}>{selectedDevice.name}</Text>
                        <Text style={styles.deviceInfo}>{selectedDevice.id}</Text>
                      </View>
                      <TouchableOpacity
                        onPress={() =>
                          isConnected
                            ?  disconnect()
                            :  connectToDevice(selectedDevice)
                        }
                        style={styles.deviceButton}>
                        <Text
                          style={styles.scanButtonText}>
                          {isConnected ? 'Disconnect' : 'Connect'}
                        </Text>
                      </TouchableOpacity>
                    </View>

          <View
              style={{
                flexDirection: 'row',
                justifyContent: 'space-between',
                margin: 5,
              }}>        
              <TextInput
                style={{
                  backgroundColor: '#888888',
                  margin: 2,
                  borderRadius: 15,
                  flex:3,
                  }}
                placeholder="Enter a message"
                value={messageToSend}
                onChangeText={(text) => setMessageToSend(text )}
              />
              <TouchableOpacity
                        onPress={() => sendMessage()
                        }
                        style={[styles.sendButton]}>
                        <Text
                          style={[
                            styles.scanButtonText,
                          ]}>
                          SEND
                        </Text>
                      </TouchableOpacity>
        </View>
              <Text>Received Message:</Text>
              <TextInput
              editable = {false}
              multiline
              numberOfLines={20}
              maxLength={300}
                style={{
                  backgroundColor: '#333333',
                  margin: 10,
                  borderRadius: 2,
                  borderWidth: 1,
                  borderColor: '#EEEEEE',
                  textAlignVertical: 'top',
                  }} >
                    {receivedMessage}
              </TextInput>
              
            </>
          )}
        </ScrollView>
      </View>
    );

Résultat

Comme l’appairage n’est pas gérer par l’application, il faut appairer l’ESP32 avant l’utilisation de l’application. Une fois le code chargé sur l’ESP32, vous pouvez lancer l’application sur le téléphone à l’aide de la commande

npx react-native start

Code complet de l’application React Native

/**
 * https://kenjdavidson.com/react-native-bluetooth-classic/
 */

import React, {useState, useEffect} from 'react';
import {   
  StyleSheet,
  Dimensions,
  View, ScrollView, Text,
   Button, TextInput,PermissionsAndroid,Platform, TouchableOpacity } from 'react-native';
import RNBluetoothClassic, {BluetoothDevice,} from 'react-native-bluetooth-classic';

const BluetoothClassicTerminal = () =>  {
  const [devices, setDevices] = useState<any[]>([]);
  const [paired, setPaired] = useState<any[]>([]);
  const [selectedDevice, setSelectedDevice] = useState<BluetoothDevice>();
  const [messageToSend, setMessageToSend] = useState("");
  const [receivedMessage, setReceivedMessage] = useState("");
  const [isConnected, setIsConnected] = useState(false);
  const [intervalId, setIntervalId] = useState<NodeJS.Timer>();
  
  
  /*const [state, setState] = useState({
    devices: [],
    paired: [],
    selectedDevice: null,
    messageToSend: "",
    receivedMessage: "",
    isConnected: false,
    intervalId: null,
  })*/




  const checkBluetoothEnabled = async () => {
    try {
      const enabled = await RNBluetoothClassic.isBluetoothEnabled();
      if (!enabled) {
        await RNBluetoothClassic.requestBluetoothEnabled();
      }
      
    } catch (error) {
      console.error('Bluetooth Classic is not available on this device.');
    }
  }
  
  const startDeviceDiscovery = async () => {
    console.log("searching for devices...");
    try {
      const paired = await RNBluetoothClassic.getBondedDevices();
      console.log("Bonded peripherals: " + paired.length);
      setPaired(paired);
    } catch (error) {
      console.error('Error bonded devices:', error);
    }
    
    /*try {
      const devices = await RNBluetoothClassic.startDiscovery();
      this.setState({ devices });
      console.log("Discovered peripherals: " + devices.length);
    } catch (error) {
      console.error('Error discovering devices:', error);
    }*/
  }

  const connectToDevice = async (device: BluetoothDevice) => {
    try {
      console.log("Connecting to device");
      let connection = await device.isConnected();
      if (!connection) {
        console.log("Connecting to device");
        await device.connect({
          connectorType: "rfcomm",
          DELIMITER: "\n",
          DEVICE_CHARSET: Platform.OS === "ios" ? 1536 : "utf-8",
        });
      }
      setSelectedDevice(device);
      setIsConnected(true);
      console.log("is connected : ",isConnected);
      //device.onDataReceived((data) => this.readData());
      //const intervalId = setInterval(() => {readData();}, 100);
      //setIntervalId(intervalId);

      
    } catch (error) {
      console.error('Error connecting to device:', error);
    }
  }

    /*async onReceivedData() {
  const { selectedDevice, receivedMessage } = this.state;
  //console.log("event : recived message", event);
  try{
    //const message = await selectedDevice.read();
    console.log("reieved msg from", selectedDevice.name);
    const messages = await selectedDevice.available();
  if (messages.length > 0) {
    console.log("msg waiting : ", messages.length);
  }
    //this.setState({ receivedMessage: message.data });
  } catch (error) {
    console.error('Error receiving data:', error);
  }
}*/

const sendMessage = async () => {
  if(selectedDevice && isConnected){
    console.log("isConnected in message",isConnected);
    try {
      await selectedDevice.write(messageToSend);
      
    } catch (error) {
      console.error('Error sending message:', error);
    }
  }
}

/*const readData = async () => {  
  console.log("reading data connected", isConnected);
  if(selectedDevice && isConnected){
    try {
      console.log("reading data from", selectedDevice.name);
      //const available = await selectedDevice.available();
      //if (available>1){
        let message = await selectedDevice.read();
        if(message){
          message = message.trim();
          if (message !== "" && message !== " "){
            console.log("reading data from", selectedDevice.name);
            //console.log(" available : ",  available);
            //console.log("available", selectedDevice.available());
            //console.log("read", selectedDevice.read());
            setReceivedMessage(receivedMessage + message +"\n" );
            console.log('message', message);
            console.log('message', receivedMessage);
            
          }
        }
    //  }

    } catch (error) {
      //console.log("isConnected",isConnected);
      //console.log("selectedDevice",selectedDevice);
      console.error('Error reading message:', error);
    }
  }
}*/

const readData = async () => {  
  if (selectedDevice && isConnected) {
    try {
      //const available = await selectedDevice.available();
      //if (available>1){
        let message = await selectedDevice.read();
        if(message){
          message = message.trim();
          if (message !== "" && message !== " "){
            if(receivedMessage.length>300){
              setReceivedMessage("");
            }
            setReceivedMessage(receivedMessage => receivedMessage + message +"\n" );
          }
        }
    //  }

    } catch (error) {
      //console.log("isConnected",isConnected);
      //console.log("selectedDevice",selectedDevice);
      console.error('Error reading message:', error);
    }
  }
}

useEffect(() => {
  let intervalId: string | number | NodeJS.Timer | undefined;
  if (selectedDevice && isConnected) {
    intervalId = setInterval(() => readData(), 100);
  }
  return () => {
    clearInterval(intervalId);
  };
}, [isConnected,selectedDevice]);

const disconnect = () => {
  //need to reset esp32 at disconnect
  if(selectedDevice && isConnected){
    try {
      clearInterval(intervalId);
      setIntervalId(undefined);
      
      selectedDevice.clear().then( () => {
        console.log("BT buffer cleared");
      });

      selectedDevice.disconnect().then( () => {
        setSelectedDevice(undefined);
        setIsConnected(false);
        setReceivedMessage("");
        console.log("Disconnected from device");
      });

      /*RNBluetoothClassic.unpairDevice(uuid).then( () => {
        console.log("Unpaired from device");
      });
      
      RNBluetoothClassic.pairDevice(uuid).then( () => {
        console.log("paired from device");
      });*/



    } catch (error) {
      console.error('Error disconnecting:', error);
    }
  }
}
  useEffect(() => {

    async function requestBluetoothPermission(){
      try {
        const grantedScan = await PermissionsAndroid.request(
          PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN,
          {
            title: 'Bluetooth Scan Permission',
            message: 'This app needs Bluetooth Scan permission to discover devices.',
            buttonPositive: 'OK',
            buttonNegative: 'Cancel',
          }
        );
  
        const grantedConnect = await PermissionsAndroid.request(
          PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT,
          {
            title: 'Bluetooth Connect Permission',
            message: 'This app needs Bluetooth Connect permission to connect to devices.',
            buttonPositive: 'OK',
            buttonNegative: 'Cancel',
          }
        );
      
        const grantedLocation = await PermissionsAndroid.request(
          PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
          {
            title: 'Fine Location Permission',
            message: 'This app needs to know location of device.',
            buttonPositive: 'OK',
            buttonNegative: 'Cancel',
          }
        );
  
        if (
          grantedScan === PermissionsAndroid.RESULTS.GRANTED &&
          grantedConnect === PermissionsAndroid.RESULTS.GRANTED &&
          grantedLocation === PermissionsAndroid.RESULTS.GRANTED
        ) {
          console.log('Bluetooth permissions granted');
          // Vous pouvez maintenant commencer la découverte et la connexion Bluetooth ici.
        } else {
          console.log('Bluetooth permissions denied');
        }
      
      
      } catch (err) {
        console.warn(err);
      }
    }

      checkBluetoothEnabled();

      requestBluetoothPermission().then( () => {
        startDeviceDiscovery();
      });
  }, [])


    return (
      <View>
      <Text
            style={{
              fontSize: 30,
              textAlign: 'center',
              borderBottomWidth: 1,
            }}>
            AC Bluetooth Terminal
          </Text>
        <ScrollView>

          {!isConnected && (
          <>
		  
		  <TouchableOpacity
            onPress={() => startDeviceDiscovery()}
            style={[styles.deviceButton]}>
            <Text
                style={[
                    styles.scanButtonText,
                    ]}>
                SCAN
            </Text>
         </TouchableOpacity>
          {/*
          <Text>Available Devices:</Text>
          {devices.map((device) => (
            <Button
              key={device.id}
              title={device.name || 'Unnamed Device'}
              onPress={() => this.connectToDevice(device)}
            />
          ))}
          */}
          <Text>Paired Devices:</Text>
          {paired.map((pair: BluetoothDevice,i) => (
                      <View  key={i}
                      style={{
                        flexDirection: 'row',
                        justifyContent: 'space-between',
                        marginBottom: 2,
                      }}>
                      <View style={styles.deviceItem}>
                        <Text style={styles.deviceName}>{pair.name}</Text>
                        <Text style={styles.deviceInfo}>{pair.id}</Text>
                      </View>
                      <TouchableOpacity
                        onPress={() =>
                          isConnected
                            ?  disconnect()
                            :  connectToDevice(pair)
                        }
                        style={styles.deviceButton}>
                        <Text
                          style={[
                            styles.scanButtonText,
                            {fontWeight: 'bold', fontSize: 12},
                          ]}>
                          {isConnected ? 'Disconnect' : 'Connect'}
                        </Text>
                      </TouchableOpacity>
                    </View>
          ))}
          </>  
          )}
          {selectedDevice && isConnected && (
            <>
              <View
                      style={{
                        flexDirection: 'row',
                        justifyContent: 'space-between',
                        margin: 5,
                      }}>
                      <View style={styles.deviceItem}>
                        <Text style={styles.deviceName}>{selectedDevice.name}</Text>
                        <Text style={styles.deviceInfo}>{selectedDevice.id}</Text>
                      </View>
                      <TouchableOpacity
                        onPress={() =>
                          isConnected
                            ?  disconnect()
                            :  connectToDevice(selectedDevice)
                        }
                        style={styles.deviceButton}>
                        <Text
                          style={styles.scanButtonText}>
                          {isConnected ? 'Disconnect' : 'Connect'}
                        </Text>
                      </TouchableOpacity>
                    </View>

          <View
              style={{
                flexDirection: 'row',
                justifyContent: 'space-between',
                margin: 5,
              }}>        
              <TextInput
                style={{
                  backgroundColor: '#888888',
                  margin: 2,
                  borderRadius: 15,
                  flex:3,
                  }}
                placeholder="Enter a message"
                value={messageToSend}
                onChangeText={(text) => setMessageToSend(text )}
              />
              <TouchableOpacity
                        onPress={() => sendMessage()
                        }
                        style={[styles.sendButton]}>
                        <Text
                          style={[
                            styles.scanButtonText,
                          ]}>
                          SEND
                        </Text>
                      </TouchableOpacity>
        </View>
              <Text>Received Message:</Text>
              <TextInput
              editable = {false}
              multiline
              numberOfLines={20}
              maxLength={300}
                style={{
                  backgroundColor: '#333333',
                  margin: 10,
                  borderRadius: 2,
                  borderWidth: 1,
                  borderColor: '#EEEEEE',
                  textAlignVertical: 'top',
                  }} >
                    {receivedMessage}
              </TextInput>
              
            </>
          )}
        </ScrollView>
      </View>
    );

};//end of component

const windowHeight = Dimensions.get('window').height;
const styles = StyleSheet.create({
  mainBody: {
    flex: 1,
    justifyContent: 'center',
    height: windowHeight,
  },

  scanButtonText: {
    color: 'white',
    fontWeight: 'bold',
    fontSize: 12,
	textAlign: 'center',
  },
  noDevicesText: {
    textAlign: 'center',
    marginTop: 10,
    fontStyle: 'italic',
  },
  deviceItem: {
    marginBottom: 2,
  },
  deviceName: {
    fontSize: 14,
    fontWeight: 'bold',
  },
  deviceInfo: {
    fontSize: 8,
  },
  deviceButton: {
    backgroundColor: '#2196F3',
    padding: 10,
    borderRadius: 10,
    margin: 2,
    paddingHorizontal: 20,
  },
  sendButton: {
  backgroundColor: '#2196F3',
  padding: 15,
  borderRadius: 15,
  margin: 2,
  paddingHorizontal: 20,
  },
});

export default BluetoothClassicTerminal;

Sources

Communication BLE avec ESP32

Communication BLE avec ESP32

Dans ce tutoriel, nous allons apprendre comment activer et gérer le Bluetooth Low Energy(BLE) sur un ESP32 en utilisant le langage de programmation Arduino.

Le Bluetooth Low Energy est une version du bluetooth à faible énergie qui permet d’envoyer des petits paquets de données à intervalle régulier.

Matériels

  • Un module ESP32 (Bluetooth+Wifi embarqué)
  • Un ordinateur avec Python installé ou smartphone
  • Câble USB pour la connexion ESP32-ordinateur

Environnement et Configuration de l’IDE

Pour programmer votre ESP32 avec l’IDE Arduino, vous pouvez suivre ce tutoriel précédent.

Communication Série via BLE

La communication BLE doit être configuré avec un certains nombre d’adresses (UIID) qui sont comme des registres mémoires dans lesquels nous allons pouvoir lire et/ou écrire. Dans l’exemple, nous définissons un service avec une caractéristique disponible en lecture et en écriture. Nous pourrons donc écrire et lire cette valeur en adressant directement l’uiid CHARACTERISTIC_UUID.

//https://github.com/espressif/arduino-esp32/tree/master/libraries/BLE

#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>

// See the following for generating UUIDs:
// https://www.uuidgenerator.net/

#define SERVICE_UUID        "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"


class MyCallbacks: public BLECharacteristicCallbacks {
    void onWrite(BLECharacteristic *pCharacteristic) {
      std::string value = pCharacteristic->getValue();

      if (value.length() > 0) {
        Serial.println("*********");
        Serial.print("New value: ");
        for (int i = 0; i < value.length(); i++)
          Serial.print(value[i]);

        Serial.println();
        Serial.println("*********");
      }
    }
};

void setup() {
  Serial.begin(115200);

  Serial.println("1- Download and install an BLE Terminal FREE");
  Serial.println("2- Scan for BLE devices in the app");
  Serial.println("3- Connect to ESP32BLE");
  Serial.println("4- Go to CUSTOM CHARACTERISTIC in CUSTOM SERVICE and write something");

  BLEDevice::init("ESP32BLE");
  BLEServer *pServer = BLEDevice::createServer();

  BLEService *pService = pServer->createService(SERVICE_UUID);

  BLECharacteristic *pCharacteristic = pService->createCharacteristic(
                                         CHARACTERISTIC_UUID,
                                         BLECharacteristic::PROPERTY_READ |
                                         BLECharacteristic::PROPERTY_WRITE
                                       );

  pCharacteristic->setCallbacks(new MyCallbacks());

  pCharacteristic->setValue("Hello World");
  pService->start();

  BLEAdvertising *pAdvertising = pServer->getAdvertising();
  pAdvertising->start();

  Serial.print("Server address:");
  Serial.println(BLEDevice::getAddress().toString().c_str());
}

void loop() {
  // put your main code here, to run repeatedly:
  delay(2000);
}

N.B.: il est possible de récupérer l’adresse MAC de l’ESP32 avec la fonction BLEDevice::getAddress().toString().c_str()

Appairage

Une fois la configuration du module effectuée comme vous le désirez, vous pouvez appairé l’ESP32 avec le système de votre choix comme n’importe quel périphérique Bluetooth. Sélectionnez le nom dans la liste des périphériques détectés (nom ESP32BLE)

Test de la communication BLE à l’aide de BLE Terminal

Nous allons tester la communication BLE à l’aide de l’application BLE Terminal

Le message est bien échangé entre le téléphone et l’ESP32 via Bluetooth LE

Communication bidirectionnel entre appareil et ESP32BLE

Voici le code permettant de modifier la valeur de la caractéristique à l’aide du moniteur série. Ainsi l’appareil connecté et le moniteur série viennent modifier la valeur de la caractéristique.

#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>

// See the following for generating UUIDs:
// https://www.uuidgenerator.net/

#define SERVICE_UUID        "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"

BLECharacteristic *pCharacteristic = NULL;

std::string msg;

class MyCallbacks: public BLECharacteristicCallbacks {
    void onWrite(BLECharacteristic *pCharacteristic) {
      std::string value = pCharacteristic->getValue();

      if (value.length() > 0) {
        Serial.println("*********");
        Serial.print("New value: ");
        for (int i = 0; i < value.length(); i++)
          Serial.print(value[i]);

        Serial.println();
        Serial.println("*********");
      }
    }
};

void setup() {
  Serial.begin(115200);

  Serial.println("1- Download and install an BLE Terminal Free");
  Serial.println("2- Scan for BLE devices in the app");
  Serial.println("3- Connect to ESP32BLE");
  Serial.println("4- Go to CUSTOM CHARACTERISTIC in CUSTOM SERVICE and write something");

  BLEDevice::init("ESP32BLE");
  BLEServer *pServer = BLEDevice::createServer();

  BLEService *pService = pServer->createService(SERVICE_UUID);

  pCharacteristic = pService->createCharacteristic(
                                         CHARACTERISTIC_UUID,
                                         BLECharacteristic::PROPERTY_READ |
                                         BLECharacteristic::PROPERTY_WRITE
                                       );

  pCharacteristic->setCallbacks(new MyCallbacks());

  pCharacteristic->setValue("Hello World");
  pService->start();

  BLEAdvertising *pAdvertising = pServer->getAdvertising();
  pAdvertising->addServiceUUID(SERVICE_UUID);
  pAdvertising->setScanResponse(true);
  pAdvertising->setMinPreferred(0x06);  // functions that help with iPhone connections issue
  pAdvertising->setMinPreferred(0x12);
  pAdvertising->start();

  Serial.print("Server address:");
  Serial.println(BLEDevice::getAddress().toString().c_str());
}

void loop() {
  readSerialPort();

  //Send data to slave
  if(msg!=""){
    pCharacteristic->setValue(msg);
    msg="";
  }
  
  delay(2000);
}

void readSerialPort(){
 while (Serial.available()) {
   delay(10);  
   if (Serial.available() >0) {
     char c = Serial.read();  //gets one byte from serial buffer
     msg += c; //add to String
   }
 }
 Serial.flush(); //clean buffer
}

En pratique, il sera certainement préférable d’utiliser un service pour l’écriture et un service pour la lecture.

Communiquer entre ESP32 et Python via BLE

Vous pouvez gérer la communication Bluetooth Low Energy depuis votre PC.

Pour cela installer le paquet Bleak

python -m pip install bleak

Detect bluetooth devices

import asyncio
from bleak import BleakScanner

async def main():
	target_name = "ESP32BLE"
	target_address = None

	devices = await BleakScanner.discover()
	for d in devices:
		print(d)
		if target_name == d.name:
			target_address = d.address
			print("found target {} bluetooth device with address {} ".format(target_name,target_address))
			break
asyncio.run(main())

Output

C1:2E:C6:8E:47:E8: None
3C:61:05:31:5F:12: ESP32BLE
found target ESP32BLE bluetooth device with address 3C:61:05:31:5F:12

Se connecter et communiquer avec l’ESP32

Voici un script Python pour se connecter automatiquement à l’appareil BLE ESP32 depuis un PC. Pour communiquer avec l’appareil BLE, il est important de connaître les uiid des différents services. Nous définissons les UUID comme ceux définis dans le code de l’ESP32

import asyncio
from bleak import BleakScanner
from bleak import BleakClient

async def main():
	target_name = "ESP32BLE"
	target_address = None

	SERVICE_UUID=        "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
	CHARACTERISTIC_UUID= "beb5483e-36e1-4688-b7f5-ea07361b26a8"

	devices = await BleakScanner.discover()
	for d in devices:
		print(d)
		if target_name == d.name:
			target_address = d.address
			print("found target {} bluetooth device with address {} ".format(target_name,target_address))
			break

	if target_address is not None:		
		async with BleakClient(target_address) as client:
			print(f"Connected: {client.is_connected}")
				
			while 1:
				text = input()
				if text == "quit":
					break

				await client.write_gatt_char(CHARACTERISTIC_UUID, bytes(text, 'UTF-8'), response=True)
				
				try:
					data = await client.read_gatt_char(CHARACTERISTIC_UUID)
					data = data.decode('utf-8') #convert byte to str
					print("data: {}".format(data))
				except Exception:
					pass
				
			
	else:
		print("could not find target bluetooth device nearby")


asyncio.run(main())

Créer des caractéristiques, descripteurs et services

Le principe du BLE repose sur l’envoie de petits paquets de données contrairement au Bluetooth classique qui permet l’envoie de données en continu. En ce sens, vous pouvez créer des services et/ou des caractéristiques différentes en fonctions des données échangées. Si, par exemple, vous avez plusieurs capteurs branchés à l’ESP32, vous pouvez créer une caractéristique par capteur.

Chaque caractéristique est identifiée par UUID (16 bytes Universally Unique IDentifier) et gérée par un service.

Chaque service peut gérer par défaut 7 caractéristiques (15 handles au total, deux pour une caractéristique et un pour un descripteur)

Il est possible d’augmenter ce nombre à la création du service

BLEService* pService = pServer->createService(BLEUUID(SERVICE_UUID), 32); // 32 available handles

Nous créons donc les variables et fonctions nécessaires pour la gestions de 3 caractéristiques supplémentaires

  • Points (pPoints, PtsCallbacks)
  • IP (pIP, IpCallbacks)
  • EEPROM (pEeprom, EeCallbacks)

Pour définir les UUD, nous utilisons le site uuid-generator

/*
    Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleWrite.cpp
    Ported to Arduino ESP32 by Evandro Copercini
*/

#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>

// See the following for generating UUIDs:
// https://www.uuidgenerator.net/

#define SERVICE_UUID        "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"

#define POINTS_UUID "ff0a1ece-8a3a-4dc8-bfb8-eae1371dc3fb"
#define IP_UUID "ff673592-0b83-4151-a871-7d222c9dab3c"
#define EEPROM_UUID "5056c1b6-f123-4ade-aa77-9b7f44c717eb"

BLECharacteristic *pCharacteristic = NULL;
BLECharacteristic* pPoints = NULL;
BLECharacteristic* pIP = NULL;
BLECharacteristic* pEeprom = NULL;

std::string msg;
bool deviceConnected = false;
bool oldDeviceConnected = false;

class MyCallbacks: public BLECharacteristicCallbacks {
    void onWrite(BLECharacteristic *pCharacteristic) {
      std::string value = pCharacteristic->getValue();

      if (value.length() > 0) {
        Serial.print("Charac value: ");
        for (int i = 0; i < value.length(); i++)
          Serial.print(value[i]);

        Serial.println();
      }
    }

};
class PtsCallbacks: public BLECharacteristicCallbacks {
    void onWrite(BLECharacteristic *pPoints) {
      std::string value = pPoints->getValue();

      if (value.length() > 0) {
        Serial.print("Points value: ");
        for (int i = 0; i < value.length(); i++)
          Serial.print(value[i]);

        Serial.println();
      }
    }    

};
class IpCallbacks: public BLECharacteristicCallbacks {   
    void onWrite(BLECharacteristic *pIP) {
      std::string value = pIP->getValue();

      if (value.length() > 0) {
        Serial.print("IP value: ");
        for (int i = 0; i < value.length(); i++)
          Serial.print(value[i]);

        Serial.println();
      }
    }    

};
class EeCallbacks: public BLECharacteristicCallbacks {
    void onWrite(BLECharacteristic *pEeprom) {
      std::string value = pEeprom->getValue();

      if (value.length() > 0) {
        Serial.print("EEPROM value: ");
        for (int i = 0; i < value.length(); i++)
          Serial.print(value[i]);

        Serial.println();
      }
    }    
    

};

class MyServerCallbacks: public BLEServerCallbacks {
  void onConnect(BLEServer* pServer) {
    Serial.println("Client connected");
  }
  void onDisconnect(BLEServer* pServer) {
    Serial.println("Client disconnected");
    BLEDevice::startAdvertising(); // needed for reconnection
  }
};

void setup() {
  Serial.begin(115200);

  Serial.println("ESP32BLE server ready");

  BLEDevice::init("ESP32BLE");
  BLEServer *pServer = BLEDevice::createServer();
  pServer->setCallbacks(new MyServerCallbacks());
  
  BLEService *pService = pServer->createService(SERVICE_UUID);

  //create BLE characteristics
  pCharacteristic = pService->createCharacteristic(
                                         CHARACTERISTIC_UUID,
                                         BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE
                                       );
  pCharacteristic->setCallbacks(new MyCallbacks());
  pCharacteristic->setValue("Hello World");

  
    // Create a BLE Characteristic
  pPoints = pService->createCharacteristic(
    POINTS_UUID,
    BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE);
  pPoints->setCallbacks(new PtsCallbacks());
  pPoints->setValue("0");
  pIP = pService->createCharacteristic(
    IP_UUID,
    BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE);
  pIP->setCallbacks(new IpCallbacks());
  pIP->setValue("0");
  pEeprom = pService->createCharacteristic(
    EEPROM_UUID,
    BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE);
  pEeprom->setCallbacks(new EeCallbacks());
  pEeprom->setValue("0");

  //start servers
  pService->start();

  BLEAdvertising *pAdvertising = pServer->getAdvertising();
  pAdvertising->addServiceUUID(SERVICE_UUID);
  pAdvertising->setScanResponse(true);
  pAdvertising->setMinPreferred(0x06);  // functions that help with iPhone connections issue
  pAdvertising->setMinPreferred(0x12);
  pAdvertising->start();

  Serial.print("Server address:");
  Serial.println(BLEDevice::getAddress().toString().c_str());
}

void loop() {
  readSerialPort();

  //Send data to slave
  if(msg!=""){
    pCharacteristic->setValue(msg);
    msg="";
  }
}

void readSerialPort(){
 while (Serial.available()) {
   delay(10);  
   if (Serial.available() >0) {
     char c = Serial.read();  //gets one byte from serial buffer
     msg += c; //add to String
   }
 }
 Serial.flush(); //clean buffer
}

N.B.: lorsque vous modifier les services et caractéristiques de l’appareil, vous devez l’appairer de nouveau

13:02:48.359 -> ESP32BLE server ready
13:02:49.049 -> Server address:3c:61:05:31:5f:12
13:02:53.967 -> Client connected
13:02:59.679 -> Client disconnected
13:03:16.293 -> Client connected
13:08:15.321 -> Points value: myPoints
13:08:28.964 -> Client disconnected
13:08:34.952 -> Client connected
13:08:48.883 -> IP value: myipaddress
13:08:53.353 -> Client disconnected
13:08:58.916 -> Client connected
13:09:07.574 -> EEPROM value: myeepromval

Application

Sources

Communication BLE avec ESP32

Communication Bluetooth avec ESP32

Dans ce tutoriel, nous allons apprendre comment activer, gérer et tester le Bluetooth sur un ESP32 en utilisant le langage de programmation Arduino. Le Bluetooth est une technologie sans fil largement utilisée pour la communication entre dispositifs électroniques. Elle permet très rapidement de transformer votre système en objet connecté.

Matériels

  • Un module ESP32 (Bluetooth+Wifi embarqué)
  • Un ordinateur avec Python installé ou smartphone
  • Câble USB pour la connexion ESP32-ordinateur

Environnement et Configuration de l’IDE

Pour programmer votre ESP32 avec l’IDE Arduino, vous pouvez suivre ce tutoriel précédent.

Récupérer l’adresse MAC

Cette information n’est pas forcément nécessaire mais il est toujours bon de savoir comment récupérer l’adresse MAC sur l’ESP32

#include "esp_bt_main.h"
#include "esp_bt_device.h"
#include "BluetoothSerial.h"

BluetoothSerial SerialBT;

void printDeviceAddress() {
  const uint8_t* point = esp_bt_dev_get_address();
  for (int i = 0; i < 6; i++) {
    char str[3];
    sprintf(str, "%02X", (int)point[i]);
    Serial.print(str);
    if (i < 5){
      Serial.print(":");
    }
  }
}
void setup() {
  Serial.begin(115200);
  SerialBT.begin("ESP32BT");
  Serial.print("MaxAddr : ");
  printDeviceAddress();

}

void loop() {}

Output

14:42:43.448 -> MaxAddr : 3C:61:05:31:5F:12

Communication Série via Bluetooth

La communication Bluetooth s’active comme la communication série. La méthode est similaire pour le module HC-06

#include "BluetoothSerial.h"

#if !defined(CONFIG_BT_ENABLED) || !defined(CONFIG_BLUEDROID_ENABLED)
#error Bluetooth is not enabled! Please run `make menuconfig` to and enable it
#endif

BluetoothSerial SerialBT;

void setup() {
  Serial.begin(115200);
  SerialBT.begin("ESP32BT"); //Bluetooth device name
  Serial.println("The device started, now you can pair it with bluetooth!");
}

void loop() {
  if (Serial.available()) {
    SerialBT.write(Serial.read());
  }
  if (SerialBT.available()) {
    Serial.write(SerialBT.read());
  }
  delay(20);
}

N.B.: Il semble qu’il y ait un moyen d’ajouter un code PIN mais impossible de le faire marcher.

SerialBT.setPin(pin);
SerialBT.begin("ESP32BT", true); //BT with PIN

Appairage

Une fois la configuration du module effectuée comme vous le désirez, vous pouvez appairé l’ESP32 avec le système de votre choix comme n’importe quel périphérique Bluetooth. Sélectionnez le nom dans la liste des périphériques détectés (nom ESP32BT)

Test de la communication Bluetooth à l’aide de Serial Bluetooth Terminal

Nous allons tester la communication bluetooth à l’aide de l’application Serial Bluetooth Terminal

Le message est bien échangé entre le téléphone et l’ESP32 via Bluetooth

Code pour récupérer la commande complète

Dans le code précédent, on faisait une copie byte par byte du message pour le renvoyer au moniteur. Ici nous allons enregistrer la commande complète dans un String msg. Cela vous permettra d’analyser la commande et de définir l’action correspondante (ex: allumer/éteindre une LED)

#include "BluetoothSerial.h"

#if !defined(CONFIG_BT_ENABLED) || !defined(CONFIG_BLUEDROID_ENABLED)
#error Bluetooth is not enabled! Please run `make menuconfig` to and enable it
#endif
String msg;

BluetoothSerial SerialBT;
const char *pin = "1234";

void setup() {
  Serial.begin(115200);
  SerialBT.begin("ESP32BT"); //Bluetooth device name
  Serial.println("ESP32BT device started, now you can pair it!");
}

void loop(){
  readSerialPort();
  
  // Send answer to master
  if(msg!=""){
    Serial.print("Master sent : " );
    Serial.println(msg);
    SerialBT.print("received : "+msg);
    msg=""; 
  }
}

void readSerialPort(){
 while (SerialBT.available()) {
   delay(10); 
   if (SerialBT.available() >0) {
     char c = SerialBT.read();  //gets one byte from serial buffer
     msg += c; //makes the string readString
   }
 }
 SerialBT.flush();
}

Communiquer entre ESP32 et Python via Bluetooth

Vous pouvez gérer la communication Bluetooth depuis votre PC.

Pour cela installer le paquets PyBluez

python -m pip install pybluez

Detect bluetooth devices

import bluetooth

target_name = "ESP32BT"
target_address = None

nearby_devices = bluetooth.discover_devices(lookup_names=True,lookup_class=True)
print(nearby_devices)
for btaddr, btname, btclass in nearby_devices:
	if target_name == btname:
		target_address = btaddr
		break

if target_address is not None:
	print("found target {} bluetooth device with address {} ".format(target_name,target_address))
else:
	print("could not find target bluetooth device nearby")

Output

[('88:C6:26:91:30:84', 'UE\xa0BOOM\xa02', 2360344), ('88:C6:26:7E:F2:7A', 'UE\xa0BOOM\xa02', 2360344), ('4C:EA:AE:D6:92:08', 'OPPO A94 5G', 5898764), ('41:42:DD:1F:45:69', 'MX_light', 2360344), ('3C:61:05:31:5F:12', 'ESP32BT', 7936)]
found target ESP32BT bluetooth device with address 3C:61:05:31:5F:12

Se connecter et communiquer avec l’ESP32

Voici un script Python pour se connecter automatiquement à l’appareil Bluetooth ESP32 depuis un PC

import bluetooth
import socket

target_name = "ESP32BT"
target_address = None

nearby_devices = bluetooth.discover_devices(lookup_names=True,lookup_class=True)
print(nearby_devices)
for btaddr, btname, btclass in nearby_devices:
	if target_name == btname:
		target_address = btaddr
		break

if target_address is not None:
	print("found target {} bluetooth device with address {} ".format(target_name,target_address))

	""" # With PyBluez NOT WORKING
	serverMACAddress = target_address
	port = 1
	s = bluetooth.BluetoothSocket(bluetooth.RFCOMM)
	s.connect((serverMACAddress, port))
	while 1:
		text = raw_input() # Note change to the old (Python 2) raw_input
		if text == "quit":
			break
		s.send(text)
		data = s.recv(1024)
		if data:
			print(data)
	sock.close()"""
	
	
	
	serverMACAddress = target_address
	port = 1
	s = socket.socket(socket.AF_BLUETOOTH, socket.SOCK_STREAM, socket.BTPROTO_RFCOMM)
	s.connect((serverMACAddress,port))
	print("connected to {}".format(target_name))
	while 1:
		text = input()
		if text == "quit":
			break
		s.send(bytes(text, 'UTF-8'))
		data = s.recv(1024)
		if data:
			print(data)
	s.close()

else:
	print("could not find target bluetooth device nearby")



N.B.: Seul la libraire socket fonctionne pour la communication Bluetooth. Il semble y avoir un soucis de maintenance sur la librairie PyBluez

Applications

Sources

Votre Arduino communique avec le module HC-05

Votre Arduino communique avec le module HC-05

Votre carte Arduino peut communiquer avec d’autres appareils(Smartphone, ordinateur ou un autres microcontrôleurs) par liaison Bluetooth en utilisant un module HC-05 (maître/esclave). Cela vous permet de connecter la carte Arduino à un autre système afin d’envoyer et de recevoir des données. La communication par Bluetooth permet, par exemple, de piloter votre robot via une application Smartphone, récupérer les mesures d’un capteur sur votre ordinateur ou encore permettre à deux robots de communiquer entre eux.

Prérequis : Communication Série avec Arduino

Matériel

  • Ordinateur
  • Arduino UNO x2
  • câble USB pour connecter l’Arduino à l’ordinateur x2
  • Module Bluetooth HC-05 ZS-040 x2 (ou 1x HC-06 et 1x HC-05)
  • Câbles Dupont M/F

Présentation du module Bluetooth HC-05

Le module Bluetooth HC-05 présente 6 broches pour permettre d’établir la connexion.

  • VCC broche d’alimentation. Typiquement connectée à la broche 5V de l’Arduino.
  • GND masse. Typiquement connectée à la broche GND de l’Arduino
  • RX broche de réception. Typiquement connecté à la broche de transmission (TX) de l’Arduino
  • TX broche de transmission. Typiquement connecté à la broche de réception (RX) de l’Arduino
  • State retourne 1 lorsque le module est connecté
  • Key ou EN doit être alimentée pour entrer dans le mode de configuration et ne doit pas être connecté pour être en mode communication.

La particularité du module Bluetooth HC-05 est qu’il peut être utilisé en mode esclave (dans ce cas il est équivalent à un module HC-06 et s’utilise de la même manière) ou en mode maître, ce qui veut dire qu’il peut de manière autonome se connecté à un autre module Bluetooth (HC-06, HC-05 en mode esclave ou autre) sans action extérieure.

Schéma de câblage pour la configuration


ATTENTION :

  • Nous utilisons les broches 2 et 3 pour la communication série avec Arduino UNO. Selon la carte que vous utilisez, ces broches peuvent ne pas être compatibles avec la communication SoftwareSerial. Notamment, pour les carte Mega, Micro et leonardo. Vérifiez la documentation.
  • La logique du module Bluetooth étant en 3,3V, il est conseillé d’utiliser un pont diviseur de tension afin de réduire la tension de 5V de l’Arduino (résistance 1k entre pin3 et Rx, et 2k Ohm entre Rx et GND).

Configuration du module HC-05

La configuration du module Bluetooth peut être intéressante pour vérifier le bon fonctionnement du module et pour modifier ses paramètres notamment lorsque vous voulez utiliser le module en mode maître. Le module doit être alimenté mais non-appairé et la broche Key/EN doit être à alimentée à 3V à l’allumage du module. Lorsque le module est en mode configuration, la LED s’allume deux secondes toutes les deux secondes.

Ouvrez le moniteur série de l’Arduino et assurez vous que l’option de fin de ligne affiche “Nouvelle ligne”  et que le baudrate est réglé à 9600.

Notez que le module Bluetooth HC-05 entre dans le mode AT avec une vitesse de communication (baudrate) de 38400 bps.

Code de configuration

Pour gérer la communication avec le module HC-05, nous utilisons la librairie SoftwareSerial.h qui permet de créer un port série autre que celui utilsé par le port USB. Le code suivant permet de modifier les paramètres du module HC-05 (nom, le code PIN, la vitesse de communication(baudrate), etc.) et de trouver des informations comme le numéro de version du firmware.

#include <SoftwareSerial.h>
#define rxPin 2
#define txPin 3
#define baudrate 38400

String msg;

SoftwareSerial hc05(rxPin ,txPin);

void setup(){
  pinMode(rxPin,INPUT);
  pinMode(txPin,OUTPUT);
  
  Serial.begin(9600);
  Serial.println("ENTER AT Commands:");
  hc05.begin(baudrate);
}

void loop(){
    readSerialPort();
    if(msg!="") hc05.println(msg);
    
    if (hc05.available()>0){
      Serial.write(hc05.read());
    }
}

void readSerialPort(){
  msg="";
 while (Serial.available()) {
   delay(10);  
   if (Serial.available() >0) {
     char c = Serial.read();  //gets one byte from serial buffer
     msg += c; //makes the string readString
   }
 }
}

Pour une bonne communication, assurez-vous de sélectionner le bon baudrate dans le moniteur série ainsi que “nouvelle ligne”(NL) dans les paramètres de communication.

Commandes de configuration

Autre le fait qu’il puisse être utilisé en mode maître, le module Bluetooth HC-05 vous permet aussi de connaître ses paramètres enregistrés.

en général, si vous tapez dans le moniteur série AT+<command>?, vous obtenez la valeur enregistré dans le module (ex: AT+PSWD? pour connaitre le code PIN du module). Si vous entrez la ligne AT+<command>=<Param>, vous définissez la valeur du module (ex: AT+PWSD=0000 pour modifier le code PIN en 0000).

Voici certaines commandes AT de base à connaître:

  • Pour tester la communication, tapez AT dans le monitor série de l’IDE Arduino. Si tout va bien, le module doit répondre OK.
  • Pour modifier le nom du module, tapez AT+NAME=<Param> module. Le module devrait répondre OK. (Ex: Pour changer le nom du module en BTM1 tapez AT+NAME=BTM1)
  • Pour modifier le code PIN du module, tapez AT+PSWD=<Param> . Le module devrait répondre OK. (Ex: si vous voulez changer le PIN en 0000 tapez AT+PSWD=0000)
  • AT+ROLE=<Param> pour modifier le role du module esclave ou maître (Ex: pour passer le module en maître AT+ROLE=1).
  • Pour modifier la vitesse de communication du module (seulement si nécessaire), tapez AT+UART=<Param1>,<Param2>,<Param3> avec Param1, 2 et 3 les paramètres de communication série: le baud rate, le bit d’arrêt et le bit de parité  respectivement. (Par défaut, 9600,0,0. Ex: si vous voulez changer le baudrate en 115200 tapez AT+UART=115200,0,0).

Ils existent d’autres commandes AT du module Bluetooth HC-05 que vous pouvez retrouver en suivant le lien.

ATTENTION: Différentes versions du module HC-05 existent et la liste des commandes AT et la procédure peuvent varier. Vérifiez bien le numéro de série écrit sur le module et la version du firmware en tapant la commande AT+VERSION?. Dans notre cas le module HC-05 est libellé ZS-040 et la version est 2.0-20100601.

N.B.: Si ça ne fonctionne pas vérifiez le branchement, la conductivité des câbles et la vitesse de communication (baudrate).

N’hésitez pas à laisser un commentaire si vous rencontrez des difficultés pour configurer votre module Bluetooth HC-05.

Configuration du module esclave

Pour le mode esclave, vous pouvez changer le nom en tapant AT+NAME=HC05-Slave et choisir les paramètres de communication et le mot de passe que vous voulez. Il faudra vous assurer qu’ils sont les mêmes que pour le module maître.

  • AT retourne OK
  • AT+NAME=HC05-Slave
  • AT+UART=9600,0,0
  • AT+ROLE=0
  • Entrez AT+ADDR pour obtenir l’adresse du module esclave (dans notre cas, +ADDR:98d3:32:21450e)

Pour le module esclave, vous pouvez aussi utiliser un module HC-06. Pour trouver l’adresse du module HC-06, il faudra utiliser un ordinateur ou un smartphone.

Configuration du module maître

Pour le mode maître, vous devez modifier le role du module et choisir les même paramètres de communication que pour le module esclave

  • AT retourne OK
  • AT+NAME=HC05-Master
  • AT+UART=9600,0,0
  • AT+ROLE=1
  • Vous devez enregistrer l’adresse du module esclave pour que le module maître puisse s’appairer: AT+BIND=98d3,32,21450e (remplacez les deux points “:” par des virgules “,”)

Schéma de câblage pour la communication

En mode communication la broche Key/EN ne doit pas être alimentée.

Code de communication

Lorsque les modules sont configurés, vous pouvez débrancher la pin Key ou EN, charger les codes suivant dans deux cartes Arduino et rallumer vos modules HC-05 pour entrer en mode communication.

Comme pour le module HC-06, nous utilisons la librairie SoftwareSerial.h dont voici quelques fonctions:

  • SoftwareSerial hc05(Rx,Tx) pour définir le port série et les broches servant à la communication
  • hc05.begin() pour définir la vitesse de communication
  • hc05.available() pour tester si des données sont disponible dans le buffer du port série
  • hc05.read() pour lire les données du port série, un octet à la fois
  • hc05.print() pour envoyer une chaine de caractères en ASCII
  • hc05.write() pour envoyer des données, un octet à la fois.
  • hc05.flush() pour vider les données du buffer.

Code Maître

#include <SoftwareSerial.h>

SoftwareSerial ArduinoSlave(2,3);
String answer;
String msg;

void setup(){

  Serial.begin(9600);
  Serial.println("ENTER Commands:");
  ArduinoSlave.begin(9600);
                
}

void loop(){
  //Read command from monitor
  readSerialPort();
  
  //Read answer from slave
   while (ArduinoSlave.available()) {
   delay(10);  
   if (ArduinoSlave.available() >0) {
     char c = ArduinoSlave.read();  //gets one byte from serial buffer
     answer += c; //makes the string readString
   }
 }
  //Send data to slave
  if(msg!=""){
    Serial.print("Master sent : ");
    Serial.println(msg);
    ArduinoSlave.print(msg);
    msg="";
  }
  //Send answer to monitor
  if(answer!=""){
    Serial.print("Slave recieved : ");
    Serial.println(answer);
    answer="";
  }
}

void readSerialPort(){
 while (Serial.available()) {
   delay(10);  
   if (Serial.available() >0) {
     char c = Serial.read();  //gets one byte from serial buffer
     msg += c; //makes the string readString
   }
 }
 Serial.flush();
}

Code Esclave

#include <SoftwareSerial.h>

SoftwareSerial ArduinoMaster(2,3);
String msg;

void setup(){
  Serial.begin(9600);
  ArduinoMaster.begin(9600);    
}

void loop(){
  readSerialPort();
  
  // Send answer to master
  if(msg!=""){
    Serial.print("Master sent : " );
    Serial.println(msg);
    ArduinoMaster.print(msg);
    msg=""; 
  }
}

void readSerialPort(){
 while (ArduinoMaster.available()) {
   delay(10); 
   if (ArduinoMaster.available() >0) {
     char c = ArduinoMaster.read();  //gets one byte from serial buffer
     msg += c; //makes the string readString
   }
 }
 ArduinoMaster.flush();
}

Si vous ouvrez les deux moniteurs série correspondant et si vous tapez une commande dans le moniteur de la carte maître, vous pouvez voir le résultat suivant: la carte maître envoie une chaîne de caractères que la carte esclave reçoit et renvoie. On vérifie ainsi que la communication se passe bien dans les deux sens.

Si vous avez des difficultés à configurer ou à communiquer avec votre module Bluetooth HC-05, n’hésitez pas à nous contacter ou à laisser un commentaire.

Application

Et vous, que feriez-vous si votre carte Arduino pouvait communiquer avec d’autres systèmes? N’hésitez pas à nous répondre en commentaire.

Source

Retrouvez nos tutoriels et d’autres exemples dans notre générateur automatique de code
La Programmerie