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.

/*
    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.

  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

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

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

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

  <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()

    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

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

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

/**
* 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