fbpixel
Etiquetas: , , ,

Vamos ver como criar uma aplicação React Native para Adnroid que permite a comunicação BLE (Bluetooth Low Energy) com um ESP32. Utilizaremos o React Native para desenvolver um terminal BLE no Android que pode comunicar com um ESP32 NodeMCU ou qualquer outro dispositivo compatível.

Hardware

  • Um computador com o React Native e o Node.js instalados
  • Um dispositivo Android com BLE
  • Um cabo USB para ligar o computador ao dispositivo
  • Um dispositivo BLE (ESP32)

Código de gestão BLE para ESP32

Para testar a aplicação React Native, vamos utilizar o código de gestão BLE para o 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
}

Adicionamos a função BLEServerCallbacks à gestão do servidor BLE para detetar a desconexão e iniciar a publicidade para poder voltar a ligar o ESP32.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
pServer->setCallbacks(new MyServerCallbacks());
pServer->setCallbacks(new MyServerCallbacks());
  pServer->setCallbacks(new MyServerCallbacks());

Aplicação React Native para gestão BLE

Para gerir a comunicação BLE (Bluetooth Low Energy) no dispositivo Android, utilizamos a biblioteca react-native-ble-manager

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
npm install react-native-ble-manager --save
npm install react-native-ble-manager --save
npm install react-native-ble-manager --save

Para configurar o projeto de aplicação, siga o tutorial anterior.

No ficheiro App.tsx, para utilizar a biblioteca, importamo-la utilizando o comando

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';

Estamos a criar um componente funcional que conterá os elementos necessários para gerir a comunicação 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);

Nota: é possível criar um componente derivado de ReactNative.Components

Gestão de autorizações

Para descobrir e estabelecer ligação a dispositivos Bluetooth, são necessárias pelo menos 3 permissões:

  • BLUETOOTH_SCAN
  • LIGAÇÃO_DE_DISTRIBUIÇÃO
  • LOCALIZAÇÃO_DE_ACESSO

N.B.: estas permissões dependem da versão e do sistema operativo utilizado

Eis as etiquetas a adicionar ao ficheiro 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" />

No ficheiro App.tsx, criamos a função requestBluetoothPermission()

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
if (Platform.OS === 'android' &amp;&amp; Platform.Version &gt;= 23) {
PermissionsAndroid.requestMultiple([
PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN,
PermissionsAndroid.PERMISSIONS.LIGAÇÃO_DE_DISTRIBUIÇÃO,
PermissionsAndroid.PERMISSIONS.LOCALIZAÇÃO_DE_ACESSO,
]).then(result =&gt; {
if (
(result['android.permission.BLUETOOTH_SCAN'] &amp;&amp;
result['android.permission.LIGAÇÃO_DE_DISTRIBUIÇÃO'] &amp;&amp;
result['android.permission.LOCALIZAÇÃO_DE_ACESSO'] === 'granted')
||
(result['android.permission.BLUETOOTH_SCAN'] &amp;&amp;
result['android.permission.LIGAÇÃO_DE_DISTRIBUIÇÃO'] &amp;&amp;
result['android.permission.LOCALIZAÇÃO_DE_ACESSO'] === 'never_ask_again')
) {
console.log('User accepted');
} else {
console.log('User refused'); }
});
}
if (Platform.OS === 'android' &amp;&amp; Platform.Version &gt;= 23) { PermissionsAndroid.requestMultiple([ PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN, PermissionsAndroid.PERMISSIONS.LIGAÇÃO_DE_DISTRIBUIÇÃO, PermissionsAndroid.PERMISSIONS.LOCALIZAÇÃO_DE_ACESSO, ]).then(result =&gt; { if ( (result['android.permission.BLUETOOTH_SCAN'] &amp;&amp; result['android.permission.LIGAÇÃO_DE_DISTRIBUIÇÃO'] &amp;&amp; result['android.permission.LOCALIZAÇÃO_DE_ACESSO'] === 'granted') || (result['android.permission.BLUETOOTH_SCAN'] &amp;&amp; result['android.permission.LIGAÇÃO_DE_DISTRIBUIÇÃO'] &amp;&amp; result['android.permission.LOCALIZAÇÃO_DE_ACESSO'] === 'never_ask_again') ) { console.log('User accepted'); } else { console.log('User refused'); } }); }
    if (Platform.OS === 'android' &amp;&amp; Platform.Version &gt;= 23) {
  
        PermissionsAndroid.requestMultiple([
          PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN,
          PermissionsAndroid.PERMISSIONS.LIGAÇÃO_DE_DISTRIBUIÇÃO,
          PermissionsAndroid.PERMISSIONS.LOCALIZAÇÃO_DE_ACESSO,
        ]).then(result =&gt; {
          if (
            (result['android.permission.BLUETOOTH_SCAN'] &amp;&amp;
            result['android.permission.LIGAÇÃO_DE_DISTRIBUIÇÃO'] &amp;&amp;
            result['android.permission.LOCALIZAÇÃO_DE_ACESSO'] === 'granted')
            ||
            (result['android.permission.BLUETOOTH_SCAN'] &amp;&amp;
            result['android.permission.LIGAÇÃO_DE_DISTRIBUIÇÃO'] &amp;&amp;
            result['android.permission.LOCALIZAÇÃO_DE_ACESSO'] === 'never_ask_again')
          ) {
            console.log('User accepted');
          } else {
            console.log('User refused');        }
        });
    }

Função de gestão BLE

As funções de gestão do Bluetooth LE são as seguintes

  • descobrir dispositivos bluetooth startDeviceDiscovery() (utilizo dispositivos emparelhados)
  • ligar ao dispositivo connectToDevice()
  • disconnectFromDevice()
  • enviar mensagens sendMessage()
  • ler mensagens da comunicação readData()

N.B.: Neste exemplo, estamos a escrever e a ler a partir da mesma caraterística. Assim, lemos o valor registado premindo o botão

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);
   });
 };

A função de renderização do ecrã

Para a apresentação, decidimos colocar tudo no mesmo ecrã. Haverá :

  • Um título
  • 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 para escrever a mensagem a enviar messageToSend
    • Um botão de envio
    • Um botão de reprodução
    • Uma caixa de texto para apresentar receivedMessage
    • Um botão para desligar
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>
    );

Resultados

Como o emparelhamento não é gerido pela aplicação, o ESP32 tem de estar emparelhado antes de utilizar a aplicação. Uma vez carregado o código no ESP32, é possível lançar a aplicação no telemóvel utilizando o comando

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
npx react-native start
npx react-native start
npx react-native start
react-native-ble-discovery Criar uma aplicação BLE para o ESP32 com React Native
react-native-ble-terminal Criar uma aplicação BLE para o ESP32 com React Native
react-native-ble-esp32-monitor Criar uma aplicação BLE para o ESP32 com React Native

Código completo para a aplicação 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;

Fontes