Flutter 1. ESP32와 블루투스 통신하기

2024. 3. 28. 12:44· 캡스톤디자인/모바일 프론트엔드
목차
  1. 1. VScode에서 Flutter 프로젝트 생성
  2. 2. Flutter BLE 기본 설정(안드로이드만)
728x90

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월 버전으로 성공적으로 수행했다. 다음시간에는 프로젝트 메인 화면 구성을 하겠다.

728x90
  1. 1. VScode에서 Flutter 프로젝트 생성
  2. 2. Flutter BLE 기본 설정(안드로이드만)
조찬국
조찬국
개발자가 되기위해 매일매일 체득하며 배우는 학생입니다.
changuk.log개발자가 되기위해 매일매일 체득하며 배우는 학생입니다.
조찬국
changuk.log
조찬국
전체
오늘
어제
  • 분류 전체보기 (68)
    • Spring Boot (30)
      • Spring Boot 입문 강의 정리(김영한) (8)
      • Spring Boot JPA-기본편 강의 정리(.. (8)
      • Spring Boot JPA-활용편1 강의 정리 (7)
      • Spring Boot JPA-활용편2 강의 정리 (5)
      • Spring Boot 블로그(Blog) 만들기 프.. (2)
      • Spring Boot -Project 정리(Bac.. (0)
    • SQLD (2)
      • 데이터 모델링 (1)
      • 데이터 모델과 SQL (1)
    • 프로젝트 (1)
      • 플러터 (1)
      • 안드로이드 스튜디오 (0)
    • 캡스톤디자인 (2)
      • IoT 및 하드웨어 (1)
      • 모바일 프론트엔드 (1)
      • 백엔드 (0)
    • CS스터디 (5)
    • 수업자료 (13)
      • sw공학 (5)
      • database (8)
    • Chatbot 프로젝트 (13)
      • RAG (3)
      • LangChain (9)
      • FastAPI (1)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • LCEL
  • langchain
  • Spirng Boot
  • embeddings
  • restapi
  • SQLD
  • computer sience
  • LLM
  • 선형 자료 구조
  • retrievers
  • prompt tempate
  • 관계
  • Spring
  • 티스토리챌린지
  • RAG
  • Blog
  • ese32
  • 오블완
  • lanchain
  • text splitter
  • Chat bot
  • vector store
  • CS
  • document loader
  • spring boot
  • 비선형 자료 구조
  • uvicorn
  • chatbot
  • SQL
  • 김영한

최근 댓글

최근 글

hELLO · Designed By 정상우.v4.2.2
조찬국
Flutter 1. ESP32와 블루투스 통신하기
상단으로

티스토리툴바

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.