본문 바로가기

졸업 프로젝트

[React native - Expo] Bluetooth-Classic (1) 디바이스 스캔

BLE는 내가 추가하는 기능을 해낼 수 없어  Bluetooth-Classic으로 재도전 한다.

 

1. 패키지 설치하기

npm install react-native-bluetooth-classic

npx expo install expo-build-properties

npm install expo-dev-client

 

2. 디바이스 스캔 및 연결

useBlutoothClassic.js
import { useEffect, useState, useRef } from 'react';
import axios from "axios";
import AsyncStorage from "@react-native-async-storage/async-storage";
import BluetoothClassic from 'react-native-bluetooth-classic';
import BluetoothRequestPermissions from './BluetoothRequestPermissions';

const UseBluetoothClassic = () => {
  const discoveredDevices = useRef(new Map()); // 장치를 저장할 Map
  const [allDevices, setAllDevices] = useState([]); // 연결 가능한 주변 Bluetooth 장치 목록
  const [connectedDevice, setConnectedDevice] = useState(null); // 연결된 장치
  const [isScanning, setIsScanning] = useState(false); // 스캔 상태

  const [token, setToken] = useState(null);

  // AsyncStorage에서 토큰 가져오기
  useEffect(() => {
    const fetchToken = async () => {
      try {
        const storedToken = await AsyncStorage.getItem('token');
        setToken(storedToken);
      } catch (error) {
        console.error('토큰 가져오기 실패:', error);
      }
    };

    fetchToken();
  }, []);

  const scanForPeripherals = async () => {
    if (BluetoothClassic.isScanning) {
      return;
    }

    discoveredDevices.current.clear(); // 새로운 스캔 시작 시 기존 장치 목록 초기화
    setIsScanning(true); // 스캔 시작

    try {
      // Bluetooth 활성화 확인
      const isEnabled = await BluetoothClassic.isBluetoothEnabled();
      if (!isEnabled) {
        console.error("블루투스가 활성화 되지 않았습니다.");
        return;
      }

      // Bluetooth 장치 검색 시작
      BluetoothClassic.startDiscovery();

      // 장치 발견 이벤트 처리
      const onDeviceDiscovered = (device) => {
        if (device.name !== device.id) { // 대개 폰이면 id와 name이 같지 않음
          if (!discoveredDevices.current.has(device.id)) {
            discoveredDevices.current.set(device.id, device);

            // 상태 업데이트 (중복 없이)
            setAllDevices((prevDevices) => {
              // 장치가 기존 목록에 없으면 추가
              if (!prevDevices.some((prevDevice) => prevDevice.id === device.id)) {
                console.log(`새로운 장치 추가: ${device.name}`);
                return [...prevDevices, device];
              }
              return prevDevices;
            });
          }
        }
      };

      BluetoothClassic.onDeviceDiscovered(onDeviceDiscovered);

      // 10초 후 스캔 중지
      const timeoutId = setTimeout(() => {
        BluetoothClassic.cancelDiscovery(); // 스캔 중지
        setIsScanning(false); // 스캔 종료 표시
        console.log("스캔 종료 (타임아웃)");
      }, 10000);

      // 타임아웃 및 스캔 종료 처리
      return () => {
        clearTimeout(timeoutId);
        BluetoothClassic.cancelDiscovery();
        setIsScanning(false);
      };
    } catch (error) {
      console.error("스캔 오류:", error.message);
      setIsScanning(false);
    }
  };

  useEffect(() => {
    const initializeBLE = async () => {
      const permissionGranted = await BluetoothRequestPermissions();
      console.log("권한 요청 결과:", permissionGranted);

      if (permissionGranted) {
        console.log("useBluetoothClassic - Bluetooth 권한이 부여되었습니다.");
        scanForPeripherals(); // 스캔 시작
      } else {
        console.log("useBluetoothClassic - Bluetooth 권한이 거부되었습니다.");
      }
    };

    initializeBLE();

    return () => {
      if (isScanning) {
        BluetoothClassic.cancelDiscovery();
        setIsScanning(false); // 스캔 종료 표시
        console.log("스캔 종료 (클린업)");
      }
    };
  }, []);

  async function connectToDevice(deviceId) {
    try {
      // 연결 시도
      const device = await BluetoothClassic.connectToDevice(deviceId);
      if (device) {
        setConnectedDevice(device); // 연결된 장치를 상태로 저장
        console.log('장치 연결 성공:', device.name);
        return true;  // 연결 성공
      } else {
        return false; // 연결 실패
      }
    } catch (error) {
      console.log('연결 오류:', error.message);
      return false; // 연결 실패
    }
  }
  
  return {
    allDevices,
    scanForPeripherals,
    connectedDevice,
    connectToDevice
  };
};

export default UseBluetoothClassic;

 

bluetooth.js

 function Step2Screen({ route }) {
  const { permissionGranted = false, cardId } = route.params || {};

  const [recipients, setRecipients] = useState([]);
  const [recipientStatuses, setRecipientStatuses] = useState({});
  const { scanForPeripherals, connectToDevice, isConnected, sendData, successSend, allDevices } = useBLE();
  const [isScanning, setIsScanning] = useState(false);

  useEffect(() => {
    function Step2Screen({ route }) {
    setRecipients(allDevices || []);
  }, [allDevices]);
 
  const handlePressRecipient = async (id, cardId) => {
    setRecipientStatuses((prevStatuses) => ({
      ...prevStatuses,
      [id]: '요청 중...'
    }));
    
    try {    
      // 디바이스 연결 시도
      await connectToDevice(id);
      
      if (isConnected) {
        // 연결이 성공한 경우, cardId 전송 시도
        await sendData(cardId);
        
        // 4. 카드 ID 전송이 성공한 경우
        if (successSend) {
          setRecipientStatuses((prevStatuses) => ({
            ...prevStatuses,
            [id]: '공유 완료됨'
          }));
        } else {
          setRecipientStatuses((prevStatuses) => ({
            ...prevStatuses,
            [id]: '전송 실패'
          }));
        }
        
      } else {
        console.log('디바이스 연결 실패 이유 : ', error.toString());
        setRecipientStatuses((prevStatuses) => ({
          ...prevStatuses,
          [id]: '연결 실패'
        }));
      }
      
    } catch (error) {
      console.log('오류 발생:', error.toString());
      setRecipientStatuses((prevStatuses) => ({
        ...prevStatuses,
        [id]: '오류 발생'
      }));
    }
  }