졸업 프로젝트
[React Native - Expo] BLE 블루투스 기능 _ (2) 권한 설정, 디바이스 스캔
yyyujinnn
2024. 11. 5. 02:46
제가 원하는 기능은
스마트폰 디바이스끼리 블루투스 연결해서 정수를 전달하는 기능
입니다.
자세히는
발신자 - 정수 ID값 전달
수신자 - 받은 ID값으로 API 요청하기
- 권한 설정 전 패키지 다운로드
import * as Device from 'expo-device';
- App.json - 안드로이드에 블루투스와 위치 권한 추가 -> 재빌드 해야함
{
"expo": {
"android": {
"package": "com.teambenu.ssop",
"adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#ffffff"
},
"permissions": [
"READ_CONTACTS",
"WRITE_CONTACTS",
"BLUETOOTH",
"BLUETOOTH_ADMIN",
"ACCESS_FINE_LOCATION",
"ACCESS_COARSE_LOCATION"
]
},
"plugins": [
[
"react-native-ble-plx",
{
"isBackgroundEnabled": true,
"modes": [
"peripheral",
"central"
],
"bluetoothAlwaysPermission": "Allow $(PRODUCT_NAME) to connect to bluetooth devices"
}
]
]
}
}
- Managed Workflow (Expo Go): Expo에서 제공하는 API와 라이브러리만 사용할 수 있으며, 네이티브 모듈을 사용할 수 없습니다.
- Bare Workflow (npx expo run): 필요한 모든 네이티브 모듈을 사용할 수 있으며, react-native-ble-manager와 같은 외부 라이브러리를 자유롭게 사용할 수 있다.
계속 이런 오류가 발생해서
React Native와 관련된 네이티브 모듈을 사용하기 위해
expo를 나갔다!
expo eject
npx expo run:android
expo eject를 하면서 ios/android 관련 파일이 추가되는데 배포할 때 필요할까? 그렇다
- 네이티브 코드 유지: 이젝트 과정에서 생성된 모든 파일과 변경 사항은 앱이 정상적으로 작동하는 데 필요하다.
따라서 배포 전에 반드시 포함해야 합니다. - 테스트: 변경된 코드와 추가된 파일을 포함하여 앱을 충분히 테스트하여 배포 전 문제가 없도록 해야 한다.
결론적으로, 배포할 때는 이젝트 후 추가된 파일과 변경된 코드가 필수적입니다. 앱이 정상적으로 작동하도록 보장하기 위해 모든 변경 사항을 포함해야 합니다.
- 권한 설정 -> BluetoothRequestPermission.js ( 파일명이 길지만 수많은 파일 사이에서 알아보려면.. )
import { PermissionsAndroid, Platform } from 'react-native';
import * as ExpoDevice from 'expo-device';
const requestAndroid31Permissions = async () => {
const bluetoothScanPermission = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN,
{
title: "블루투스 스캔 권한 요청",
message: "블루투스 기능을 사용하려면 위치 권한이 필요합니다.",
buttonPositive: "OK",
}
);
const bluetoothConnectPermission = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT,
{
title: "블루투스 연결 권한 요청",
message: "블루투스 기능을 사용하려면 위치 권한이 필요합니다.",
buttonPositive: "OK",
}
);
const fineLocationPermission = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
{
title: "위치 권한 요청",
message: "블루투스 기능을 사용하려면 위치 권한이 필요합니다.",
buttonPositive: "OK",
}
);
return (
bluetoothScanPermission === "granted" &&
bluetoothConnectPermission === "granted" &&
fineLocationPermission === "granted"
);
};
const BluetoothRequestPermissions = async () => {
if (Platform.OS === "android") {
if ((ExpoDevice.platformApiLevel ?? -1) < 31) {
const granted = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
{
title: "위치 권한 요청",
message: "블루투스 기능을 사용하려면 위치 권한이 필요합니다.",
buttonPositive: "OK",
}
);
return granted === PermissionsAndroid.RESULTS.GRANTED;
} else {
const isAndroid31PermissionsGranted = await requestAndroid31Permissions();
return isAndroid31PermissionsGranted;
}
} else {
return true; // iOS에서는 모든 권한이 허용된 것으로 가정
}
};
export default BluetoothRequestPermissions;
- Bluetooth.js
import BluetoothRequestPermissions from "../../ble/BluetoothRequestPermissions.js";
import useBLE from '../../ble/useBLE.js'
import Toast from "react-native-toast-message";
function Step1Screen() {
const [permissionGranted, setPermissionGranted] = useState(null); // 권한 상태
// 블루투스 권한 요청
useEffect(() => {
const checkPermissions = async () => {
console.log('권한 요청 시작');
const permissionsGranted = await BluetoothRequestPermissions();
console.log('권한 요청 결과:', permissionsGranted);
setPermissionGranted(permissionsGranted); // 권한 상태 업데이트
checkPermissions();
fetchCardData();
}, []);
// 카드 선택하여 cardId를 파라미터로 같이 전송
const handleNext = (cardId) => {
if (permissionGranted) { // 상태로 설정된 permissionGranted를 직접 참조
// 권한이 허용된 경우 Step2Screen으로 이동
navigation.navigate('Step2', { permissionGranted, cardId });
} else {
Alert.alert('권한 요청', '권한이 거부되었습니다.');
console.log('권한이 거부되었습니다.');
}
};
}
function Step2Screen({ route }) {
const { permissionGranted = false, cardId } = route.params || {};
const [recipients, setRecipients] = useState([]);
const [recipientStatuses, setRecipientStatuses] = useState({});
const { scanForPeripherals, allDevices } = useBLE();
const [isScanning, setIsScanning] = useState(false);
useEffect(() => {
const startScanning = async () => {
if (permissionGranted && !isScanning) {
setIsScanning(true);
await scanForPeripherals(); // 스캔 메서드
}
};
startScanning();
}, [permissionGranted, isScanning]);
useEffect(() => {
setRecipients(allDevices || []); // 스캔한 모든 디바이스 저장
}, [allDevices]);
}
- 블루투스 디바이스 스캔 -> useBLE.js
import React, { useEffect, useState, useRef } from 'react';
import { BleManager } from 'react-native-ble-plx';
import BluetoothRequestPermissions from './BluetoothRequestPermissions';
const useBLE = () => {
const bleManager = useRef(new BleManager()).current;
const discoveredDevices = useRef(new Map()); // 장치를 저장할 Map
const [allDevices, setAllDevices] = useState([]); // 연결 가능한 주변 Bluetooth 장치 목록
const [isScanning, setIsScanning] = useState(false); // 스캔 상태
// 디바이스 스캔
const scanForPeripherals = async () => {
if (bleManager.isScanning) {
console.log("현재 스캔 중입니다.");
return;
}
discoveredDevices.current.clear(); // 새로운 스캔 시작 시 기존 장치 목록 초기화
setIsScanning(true); // 스캔 시작 표시
bleManager.startDeviceScan(null, null, (error, device) => {
if (error) {
// console.error("Scan error:", error.reason); -> 사실 오류납니다... 추후 수정
return;
}
if (device && device.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; // 중복된 경우 기존 배열 반환
});
} else {
// 이미 발견된 장치
}
} else if (device) {
// 장치 이름 없음
}
});
// 2초 후 스캔 중지
const timeoutId = setTimeout(() => {
bleManager.stopDeviceScan();
setIsScanning(false); // 스캔 종료 표시
console.log("스캔 종료 (타임아웃)");
}, 2000);
return () => {
clearTimeout(timeoutId); // 클린업 함수에서 타임아웃 제거
bleManager.stopDeviceScan(); // 스캔 중지
setIsScanning(false); // 스캔 종료 표시
};
};
useEffect(() => {
const initializeBLE = async () => {
const permissionGranted = await BluetoothRequestPermissions();
console.log("권한 요청 결과:", permissionGranted);
if (permissionGranted) {
console.log("Bluetooth 권한이 부여되었습니다.");
scanForPeripherals(); // 스캔 시작
} else {
console.log("Bluetooth 권한이 거부되었습니다.");
}
};
initializeBLE();
return () => {
if (isScanning) {
bleManager.stopDeviceScan();
setIsScanning(false); // 스캔 종료 표시
console.log("스캔 종료 (클린업)");
}
};
}, []);
return {
allDevices,
scanForPeripherals,
};
};
export default useBLE;