fbpixel
Étiquettes : , , , ,

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.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
/*
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
}
/* 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 }
/*
    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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import BleManager from 'react-native-ble-manager';
import BleManager from 'react-native-ble-manager';
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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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);
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);
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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<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" />
<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" />
  <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()

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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'); }
});
}
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'); } }); }
    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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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);
});
};
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); }); };
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
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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>
);
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> );
    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
react-native-ble-discovery Créer une application BLE pour ESP32 avec React Native
react-native-ble-terminal Créer une application BLE pour ESP32 avec React Native
react-native-ble-esp32-monitor Créer une application BLE pour ESP32 avec React Native

Code complet de l’application React Native

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
/**
* 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;
/** * 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;
/**
* 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