Flutter 1. ESP32와 블루투스 통신하기
1. VScode에서 Flutter 프로젝트 생성
1. `Ctrl`+`shift`+`P` 누른 후 `>flutter`라 입력을 한다. 그 후 `Appliaction` 선택
2. Flutter 프로젝트를 저장할 디렉토리에 이름을 설정해서 만든다.
3. 프로젝트명까지 입력하면 기본코드와 함께 Flutter 프로젝트가 생긴다.
<프로젝트 명 입력>
<기본 코드>
run 클릭 후, 원하는 애뮬레이터 선택.
실행 후, 오른쪽 +버튼 클릭 후에 값이 증가 함을 보면된다. 이제 기본 설정이 끝났으니, 본격적인 개발을 들어가 보겠다.
2. Flutter BLE 기본 설정(안드로이드만)
참고용 블로그
https://m.blog.naver.com/chandong83/222850757364
<실행 환경>
flutter sdk: 3.16.4
dart 3.2.3
flutter_blue_plus: 1.3.0
android compile sdk: 34
android kotlin version: 1.7.10
view->terminal 후 `flutter doctor` 입력
`AndroidManifest.xml`
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!--블루투스 권한-->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<!-- SDK 31 이상을 타겟팅 하는 앱 Bluetooth 권한 (Android 12+) -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> <uses-permission-sdk-23 android:name="android.permission.ACCESS_COARSE_LOCATION" android:maxSdkVersion="30" />
<uses-permission-sdk-23 android:name="android.permission.ACCESS_FINE_LOCATION" android:maxSdkVersion="30" />
<application
android:label="taba"
모바일 폰에서 직접 디버깅 하는법(애뮬레이터 사용x)
1. USB 연결
2. 설정->개발자 옵션
3.
main.dart
import 'package:flutter/material.dart';
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
import 'package:taba/device_screen.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
final title = 'Flutter BLE Scan Demo';
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: title,
home: MyHomePage(title: title),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
// // 장치명을 지정해 해당 장치만 표시되게함
final String targetDeviceName = 'ESP32 Force Sensor';
// FlutterBluePlus flutterBlue = FlutterBluePlus();
// List<BluetoothDevice> connectedDevices = [];
List<ScanResult> scanResultList = [];
bool _isScanning = false;
@override
initState() {
super.initState();
// 블루투스 초기화
// 블루투스 초기화
initBle();
}
// BLE 스캔 상태 얻기 위한 리스너
void initBle() {
// BLE 스캔 상태 얻기 위한 리스너
FlutterBluePlus.isScanning.listen((isScanning) {
_isScanning = isScanning;
setState(() {});
});
}
/*
스캔 시작/정지 함수
*/
scan() async {
if (!_isScanning) {
// 스캔 중이 아니라면
// 기존에 스캔된 리스트 삭제
scanResultList.clear();
// 스캔 시작, 제한 시간 60초
FlutterBluePlus.startScan(timeout: const Duration(seconds: 60));
// 스캔 결과 리스너
FlutterBluePlus.scanResults.listen((results) {
// List<ScanResult> 형태의 results 값을 scanResultList에 복사
scanResultList = results;
// UI 갱신
setState(() {
_isScanning = true;
});
});
}
}
Future<void> stopScan() async {
setState(() {
_isScanning = false;
});
await FlutterBluePlus.stopScan();
}
/*
여기서부터는 장치별 출력용 함수들
*/
/* 장치의 신호값 위젯 */
Widget deviceSignal(ScanResult r) {
return Text(r.rssi.toString());
}
/* 장치의 MAC 주소 위젯 */
Widget deviceMacAddress(ScanResult r) {
return Text(r.device.remoteId.toString());
}
/* 장치의 명 위젯 */
Widget deviceName(ScanResult r) {
String name = '';
if (r.device.advName.isNotEmpty) {
// device.name에 값이 있다면
name = r.device.advName;
} else if (r.advertisementData.advName.isNotEmpty) {
// advertisementData.localName에 값이 있다면
name = r.advertisementData.advName;
} else {
// 둘다 없다면 이름 알 수 없음...
name = 'N/A';
}
return Text(name);
}
/* BLE 아이콘 위젯 */
Widget leading(ScanResult r) {
return const CircleAvatar(
backgroundColor: Colors.cyan,
child: Icon(
Icons.bluetooth,
color: Colors.white,
),
);
}
/* 장치 아이템을 탭 했을때 호출 되는 함수 */
void onTap(ScanResult r) {
// 단순히 이름만 출력
print(r.device.advName);
Navigator.push(
context,
MaterialPageRoute(builder: (context) => DeviceScreen(device: r.device)),
);
}
/* 장치 아이템 위젯 */
Widget listItem(ScanResult r) {
return ListTile(
onTap: () => onTap(r),
leading: leading(r),
title: deviceName(r),
subtitle: deviceMacAddress(r),
trailing: deviceSignal(r),
);
}
/* UI */
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
/* 장치 리스트 출력 */
child: ListView.separated(
itemCount: scanResultList.length,
itemBuilder: (context, index) {
return listItem(scanResultList[index]);
},
separatorBuilder: (BuildContext context, int index) {
return const Divider();
},
),
),
/* 장치 검색 or 검색 중지 */
floatingActionButton: FloatingActionButton(
onPressed: _isScanning ? stopScan : scan,
// 스캔 중이라면 stop 아이콘을, 정지상태라면 search 아이콘으로 표시
child: Icon(_isScanning ? Icons.stop : Icons.search),
),
);
}
}
device_screen.dart
결과
위의 코드들을 통해 모든 BLE 연결 가능한 디바이스를 검색한 후 잘 작동하는지 확인했다.
다음으로는 main.dart 를 수정해서, 아두이노 IDE를 통해 설정한 디바이스명 `ESP32 Forse Sensor`만 찾도록 해보겠다.
`main.dart`
import 'package:flutter/material.dart';
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
import 'package:taba/device_screen.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
final title = 'Flutter BLE Scan Demo';
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: title,
home: MyHomePage(title: title),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
// // 장치명을 지정해 해당 장치만 표시되게함
final String targetDeviceName = 'ESP32 Force Sensor';
// FlutterBluePlus flutterBlue = FlutterBluePlus();
// List<BluetoothDevice> connectedDevices = [];
List<ScanResult> scanResultList = [];
bool _isScanning = false;
@override
initState() {
super.initState();
// 블루투스 초기화
// 블루투스 초기화
initBle();
}
// BLE 스캔 상태 얻기 위한 리스너
void initBle() {
// BLE 스캔 상태 얻기 위한 리스너
FlutterBluePlus.isScanning.listen((isScanning) {
_isScanning = isScanning;
setState(() {});
});
}
/*
스캔 시작/정지 함수
*/
scan() async {
if (!_isScanning) {
// 스캔 중이 아니라면
// 기존에 스캔된 리스트 삭제
scanResultList.clear();
// 스캔 시작, 제한 시간 60초
FlutterBluePlus.startScan(timeout: const Duration(seconds: 60));
// 스캔 결과 리스너
FlutterBluePlus.scanResults.listen((results) {
setState(() {
scanResultList.clear();
for (var result in results) {
if (result.device.advName == targetDeviceName) {
if (!scanResultList.any((element) =>
element.device.remoteId == result.device.remoteId)) {
scanResultList.add(result);
}
}
}
});
});
} else {
// 스캔 중이라면 스캔 정지
await FlutterBluePlus.stopScan();
}
}
/*
여기서부터는 장치별 출력용 함수들
*/
/* 장치의 신호값 위젯 */
Widget deviceSignal(ScanResult r) {
return Text(r.rssi.toString());
}
/* 장치의 MAC 주소 위젯 */
Widget deviceMacAddress(ScanResult r) {
return Text(r.device.remoteId.toString());
}
/* 장치의 명 위젯 */
Widget deviceName(ScanResult r) {
String name = '';
if (r.device.advName.isNotEmpty) {
// device.name에 값이 있다면
name = r.device.advName;
} else if (r.advertisementData.advName.isNotEmpty) {
// advertisementData.localName에 값이 있다면
name = r.advertisementData.advName;
} else {
// 둘다 없다면 이름 알 수 없음...
name = 'N/A';
}
return Text(name);
}
/* BLE 아이콘 위젯 */
Widget leading(ScanResult r) {
return const CircleAvatar(
backgroundColor: Colors.cyan,
child: Icon(
Icons.bluetooth,
color: Colors.white,
),
);
}
/* 장치 아이템을 탭 했을때 호출 되는 함수 */
void onTap(ScanResult r) {
// 단순히 이름만 출력
print(r.device.advName);
Navigator.push(
context,
MaterialPageRoute(builder: (context) => DeviceScreen(device: r.device)),
);
}
/* 장치 아이템 위젯 */
Widget listItem(ScanResult r) {
return ListTile(
onTap: () => onTap(r),
leading: leading(r),
title: deviceName(r),
subtitle: deviceMacAddress(r),
trailing: deviceSignal(r),
);
}
/* UI */
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
/* 장치 리스트 출력 */
child: ListView.separated(
itemCount: scanResultList.length,
itemBuilder: (context, index) {
return listItem(scanResultList[index]);
},
separatorBuilder: (BuildContext context, int index) {
return const Divider();
},
),
),
/* 장치 검색 or 검색 중지 */
floatingActionButton: FloatingActionButton(
onPressed: scan,
// 스캔 중이라면 stop 아이콘을, 정지상태라면 search 아이콘으로 표시
child: Icon(_isScanning ? Icons.stop : Icons.search),
),
);
}
}
결과
이렇게 해서 블루투스와 esp32 간 블루 투스 통신을 2024년 3월 버전으로 성공적으로 수행했다. 다음시간에는 프로젝트 메인 화면 구성을 하겠다.