Site icon AranaCorp

Criar uma aplicação Bluetooth para o ESP32 com React Native

Vamos ver como criar uma aplicação React Native que permite a comunicação Bluetooth entre um dispositivo Android e um ESP32. Usamos o React Native para desenvolver um terminal Bluetooth no Android que se comunica com um ESP32 NodeMCU. O NodeMCU é utilizado para testar a nossa aplicação com um objeto ligado, mas a aplicação pode funcionar com qualquer dispositivo Bluetooth.

Hardware

Código de gestão Bluetooth para ESP32

Para testar a aplicação React Native, vamos utilizar o código de gestão Bluetooth para o ESP32.

#include "BluetoothSerial.h"

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

BluetoothSerial SerialBT;

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

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

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

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

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

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

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

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

Adicionamos a função de retorno de chamada à gestão Bluetooth para detetar a desconexão e reiniciar o ESP32

SerialBT.register_callback(callback);

N.B.: é necessário reiniciar o ESP32 para voltar a ligar o Bluetooth porque, obviamente, o BluetoothSocket não se fecha quando se desliga.

Aplicação React Native para gestão de Bluetooth

Para gerir a comunicação Bluetooth (clássica) no dispositivo Android, utilizamos a biblioteca react-native-bluetooth-classic

npm install react-native-bluetooth-classic --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 RNBluetoothClassic, {BluetoothDevice,} from 'react-native-bluetooth-classic';

Estamos a criar um componente funcional que conterá os elementos de que necessitamos para gerir a comunicação Bluetooth

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

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:

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.LIGAÇÃO_DE_DISTRIBUIÇÃO" />
  <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.LOCALIZAÇÃO_DE_ACESSO" />

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

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

Função de gestão Bluetooth

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

N.B.: a documentação da biblioteca menciona a utilização de um ouvinte onDataReceived, que não consegui utilizar. Por isso, introduzi a função readData e um Intervalo para obter os dados.

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

Código completo para a aplicação React Native

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

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

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




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

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

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

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

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

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

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

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

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

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

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

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

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



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

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

      checkBluetoothEnabled();

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


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

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

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

};//end of component

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

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

export default BluetoothClassicTerminal;

Fontes

Exit mobile version