0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

flutter_blue_plus を使った「BLEスキャン+ micro:bit への接続/切断」を Flutter で実装してみる: Android Studio で Androidアプリをビルド

Last updated at Posted at 2025-03-27

はじめに

以下の記事の続きにあたる内容です。

今回やること

今回やる内容は、flutter_blue_plus を使った「BLE による Flutterアプリと micro:bit の接続/切断」です。

Android Studio で Androidアプリをビルドし、Androidスマホで動作させます。

実装

それでは早速、実装を進めます。

前回までの実装内容

冒頭に掲載していた前回記事の続きで進めていきます。

前回記事では、パーミッションの追加などの下準備をして、Flutterアプリからデバイスのスキャンを行うところまでやっていました。

接続処理の実装

スキャンの処理までは試していたので、さらに以下の公式ページに書かれた接続処理を加えます。

●flutter_blue_plus | Flutter package
 https://pub.dev/packages/flutter_blue_plus

2025-03-27_22-00-57.jpg

前回記事での実装では、スキャンした結果をアプリ上に表示していましたが、今回はその表示は行いません。

今回は「デバイス名に BBC micro:bit が含まれるデバイス」に自動で接続することにします(いったん仮の想定として、自分が用意した micro:bit が 1台だけある状況とします)。

なお、micro:bit は、「こちらの記事で実装していた BLE UART の処理など」が書き込まれたものです。

前提の補足: パーミッションの追加など

Androidアプリで BLE を使うにはパーミッションの追加などが必要です。

それについては、前回の記事をご参照ください。

コード

以下が、接続処理を実装したものです。

スキャンを実装していた際に、スキャン実行のボタンだったものが、「スキャン + micro:bit への接続」を行うものになっています。

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_blue_plus/flutter_blue_plus.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'BLE Auto Connect Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'BLE Auto Connect Demo'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});
  final String title;
  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  StreamSubscription<List<ScanResult>>? _scanSubscription;
  BluetoothDevice? _targetDevice;
  String _statusMessage = 'スキャン開始ボタンを押してください';

  Future<void> _startScan() async {
    // スキャン開始前に状態をリセット
    setState(() {
      _statusMessage = 'スキャン中...';
      _targetDevice = null;
    });

    // BluetoothがONになるのを待つ
    await FlutterBluePlus.adapterState.firstWhere(
        (state) => state == BluetoothAdapterState.on);

    bool foundDevice = false;
    _scanSubscription = FlutterBluePlus.onScanResults.listen(
      (results) async {
        for (var result in results) {
          final deviceName = result.advertisementData.advName;
          // スキャンしたデバイスのデバイス名が「BBC micro:bit」で始まるかを確認
          if (deviceName.startsWith("BBC micro:bit")) {
            if (!foundDevice) {
              foundDevice = true;
              // デバイスが見つかったらスキャンは停止
              _scanSubscription?.cancel();
              setState(() {
                _statusMessage = 'デバイス見つかりました: $deviceName\n接続中...';
              });
              BluetoothDevice device = result.device;
              _targetDevice = device;
              try {
                // 自動で接続
                await device.connect(autoConnect: true, mtu: null);
                await device.connectionState
                    .where((state) =>
                        state == BluetoothConnectionState.connected)
                    .first;
                setState(() {
                  _statusMessage = 'デバイスに接続完了: $deviceName';
                });
              } catch (e) {
                setState(() {
                  _statusMessage = '接続エラー: $e';
                });
              }
              break;
            }
          }
        }
      },
      onError: (e) {
        setState(() {
          _statusMessage = 'スキャンエラー: $e';
        });
      },
    );

    // スキャンのタイムアウトは 15秒に設定
    await FlutterBluePlus.startScan(timeout: const Duration(seconds: 15));
    await FlutterBluePlus.isScanning
        .firstWhere((isScanning) => isScanning == false);
    if (!foundDevice) {
      setState(() {
        _statusMessage = 'BBC micro:bit が見つかりませんでした';
      });
    }
  }

  @override
  void dispose() {
    _scanSubscription?.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Text(
          _statusMessage,
          textAlign: TextAlign.center,
          style: const TextStyle(fontSize: 18),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _startScan,
        tooltip: 'BLEスキャン',
        child: const Icon(Icons.search),
      ),
    );
  }
}

基本的に、前回の実装に手を加えていった形です。

切断処理の追加

続いて、切断の処理も追加します。
追加するのは、「切断処理を実行するためのボタン」と「切断処理」です。

それらを追加したコードが以下になります。

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_blue_plus/flutter_blue_plus.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'BLE Auto Connect Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'BLE Auto Connect Demo'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});
  final String title;
  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  StreamSubscription<List<ScanResult>>? _scanSubscription;
  BluetoothDevice? _targetDevice;
  String _statusMessage = 'スキャン開始ボタンを押してください';

  // スキャン開始と micro:bit への自動接続
  Future<void> _startScan() async {
    setState(() {
      _statusMessage = 'スキャン中...';
      _targetDevice = null;
    });

    await FlutterBluePlus.adapterState.firstWhere(
        (state) => state == BluetoothAdapterState.on);

    bool foundDevice = false;
    _scanSubscription = FlutterBluePlus.onScanResults.listen(
      (results) async {
        for (var result in results) {
          final deviceName = result.advertisementData.advName;
          if (deviceName.startsWith("BBC micro:bit")) {
            if (!foundDevice) {
              foundDevice = true;
              _scanSubscription?.cancel();
              setState(() {
                _statusMessage = 'デバイス見つかりました: $deviceName\n接続中...';
              });
              BluetoothDevice device = result.device;
              _targetDevice = device;
              try {
                // autoConnect有効で接続(mtuはnull)
                await device.connect(autoConnect: true, mtu: null);
                // 接続状態が「connected」になるのを待機
                await device.connectionState
                    .where((state) =>
                        state == BluetoothConnectionState.connected)
                    .first;
                setState(() {
                  _statusMessage = 'デバイスに接続完了: $deviceName';
                });
              } catch (e) {
                setState(() {
                  _statusMessage = '接続エラー: $e';
                });
              }
              break;
            }
          }
        }
      },
      onError: (e) {
        setState(() {
          _statusMessage = 'スキャンエラー: $e';
        });
      },
    );

    // タイムアウト15秒でスキャンを実行
    await FlutterBluePlus.startScan(timeout: const Duration(seconds: 15));
    await FlutterBluePlus.isScanning
        .firstWhere((isScanning) => isScanning == false);
    if (!foundDevice) {
      setState(() {
        _statusMessage = 'BBC micro:bit が見つかりませんでした';
      });
    }
  }

  // デバイスの切断処理
  Future<void> _disconnectDevice() async {
    if (_targetDevice != null) {
      try {
        await _targetDevice!.disconnect();
        setState(() {
          _statusMessage = 'デバイスとの接続を切断しました';
          _targetDevice = null;
        });
      } catch (e) {
        setState(() {
          _statusMessage = '切断エラー: $e';
        });
      }
    } else {
      setState(() {
        _statusMessage = '接続中のデバイスはありません';
      });
    }
  }

  @override
  void dispose() {
    _scanSubscription?.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Text(
          _statusMessage,
          textAlign: TextAlign.center,
          style: const TextStyle(fontSize: 18),
        ),
      ),
      // 切断用のボタンを追加(左側がスキャン用、右側が切断用のボタン)
      floatingActionButton: Row(
        mainAxisSize: MainAxisSize.min,
        children: [
          FloatingActionButton(
            heroTag: 'scan',
            onPressed: _startScan,
            tooltip: 'BLEスキャン',
            child: const Icon(Icons.search),
          ),
          const SizedBox(width: 16),
          FloatingActionButton(
            heroTag: 'disconnect',
            onPressed: _disconnectDevice,
            tooltip: '切断',
            child: const Icon(Icons.link_off),
          ),
        ],
      ),
      floatingActionButtonLocation: FloatingActionButtonLocation.endFloat,
    );
  }
}

アプリの実行結果

アプリを実行した結果は、以下のとおりです。

Flutterアプリの説続ボタン・切断ボタンを押した後、micro:bit側の LED表示が変化しているのが分かります。

micro:bit の表示が変わっているのは、冒頭に掲載した記事内で書いているとおり、以下の実装をしているためです。

2025-03-27_22-15-21.jpg

おわりに

Flutterアプリの実装で、flutter_blue_plus を使った「スキャン」⇒「micro:bit との接続/切断」の実装を試していきました。

あとは、micro:bit と接続が行われた後、以下の部分で実装している BLE UART のデータの受け取りが行えればと思います。

2025-03-27_22-17-30.jpg

【追記】 BLE UART の受信処理も追加できました

BLE UART の受信処理も追加できました!

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?