0
1

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 を使った toio への BLE接続/切断や書き込み(音の再生)を Flutter で実装してみる: Android Studio で Androidアプリをビルド

Last updated at Posted at 2025-03-27

はじめに

最近、Flutter でのスマホアプリ開発を試しています。

さらに、最近 BLE関連の実装を試していて、それに関連した試作です。

試してみる内容

今回試す内容は、Flutterアプリ(Androidアプリ)で、toio との BLE通信を試してみるというものです。

元にする内容

その際、以前自分が書いた以下の 2つの記事の内容を元にします。

●flutter_blue_plus を使った「BLEスキャン+ micro:bit への接続/切断」を Flutter で実装してみる: Android Studio で Androidアプリをビルド - Qiita
 https://qiita.com/youtoy/items/c5b70869f152ca5d29f5

●ブラウザの Web Bluetooth API を使って toio で使える効果音のいくつかをセットで鳴らす(環境は p5.js Web Editor を利用) - Qiita
 https://qiita.com/youtoy/items/00abe2b06e23bf9e0f09

具体的には、これらの記事内に掲載している Flutterアプリの実装と、Web Bluetooth API(ブラウザ上の JavaScript)を使った実装を、合わせて ChatGPT(o3-mini-high) に読み込ませてプログラムを作成してもらいます。

プロンプトは比較的シンプルな内容で試していきました。

実際に試す

実際に試していきます。

下準備

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

そのあたりの話は、以前書いた記事の説明をご覧ください。

また、以下のパッケージを使えるようにしておいてください。

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

image.png

コード

ChatGPT に、2つの参考となるコードを参照してもらいつつ、書いてもらったコードが以下となります。
(実際には、出力されたものを動かしてみて、少し手直しをしたりなどしています)

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

/// toio のサービス UUID とサウンドキャラクタリスティック UUID
const String TOIO_SERVICE_UUID = "10b20100-5b3b-4571-9508-cf3efcd7bbae";
const String TOIO_SOUND_CHARACTERISTIC_UUID = "10b20104-5b3b-4571-9508-cf3efcd7bbae";

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'toio BLE Connect Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'toio BLE 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;
  BluetoothCharacteristic? _soundCharacteristic;
  String _statusMessage = 'スキャン開始ボタンを押してください';

  /// デバイス名に "toio" が含まれるデバイスをスキャンして接続する
  Future<void> _startScan() async {
    setState(() {
      _statusMessage = 'スキャン中...';
      _targetDevice = null;
    });

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

    bool foundDevice = false;
    _scanSubscription =
        FlutterBluePlus.onScanResults.listen((List<ScanResult> results) async {
          for (var result in results) {
            final deviceName = result.device.name;
            // デバイス名に "toio" が含まれているかチェック(大文字小文字を区別しない)
            if (deviceName.toLowerCase().contains("toio")) {
              if (!foundDevice) {
                foundDevice = true;
                _scanSubscription?.cancel();
                setState(() {
                  _statusMessage =
                  'デバイス見つかりました: $deviceName\n接続中...';
                });
                _targetDevice = result.device;
                try {
                  // 接続(autoConnect は false に設定)
                  await _targetDevice!.connect(autoConnect: false);
                  // 接続状態が connected になるのを待つ
                  await _targetDevice!.connectionState.firstWhere(
                          (state) => state == BluetoothConnectionState.connected);

                  // サービス探索
                  List<BluetoothService> services =
                  await _targetDevice!.discoverServices();
                  BluetoothService? toioService;
                  for (BluetoothService service in services) {
                    if (service.uuid.toString().toLowerCase() ==
                        TOIO_SERVICE_UUID.toLowerCase()) {
                      toioService = service;
                      break;
                    }
                  }
                  // サウンドキャラクタリスティックの取得
                  if (toioService != null) {
                    for (BluetoothCharacteristic c in toioService.characteristics) {
                      if (c.uuid.toString().toLowerCase() ==
                          TOIO_SOUND_CHARACTERISTIC_UUID.toLowerCase()) {
                        _soundCharacteristic = c;
                        break;
                      }
                    }
                  }
                  setState(() {
                    _statusMessage = 'デバイスに接続完了: $deviceName';
                  });
                } catch (e) {
                  setState(() {
                    _statusMessage = '接続エラー: $e';
                  });
                }
                break;
              }
            }
          }
        });

    // 15秒間スキャンを実施
    await FlutterBluePlus.startScan(timeout: const Duration(seconds: 15));
    await FlutterBluePlus.isScanning
        .firstWhere((isScanning) => isScanning == false);
    if (!foundDevice) {
      setState(() {
        _statusMessage = 'toio が見つかりませんでした';
      });
    }
  }

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

  /// 効果音を鳴らす処理(サウンドキャラクタリスティックに書き込み)
  Future<void> _playSound() async {
    if (_soundCharacteristic == null) {
      setState(() {
        _statusMessage = 'サウンドキャラクタリスティックが取得できていません';
      });
      return;
    }
    try {
      // 指定の遅延後に書き込みを実施
      await Future.delayed(const Duration(milliseconds: 200));
      await _soundCharacteristic!
          .write([0x02, 0x04, 0xff], withoutResponse: false);
      await Future.delayed(const Duration(milliseconds: 800));
      await _soundCharacteristic!
          .write([0x02, 7, 0xff], withoutResponse: false);
      await Future.delayed(const Duration(milliseconds: 800));
      await _soundCharacteristic!
          .write([0x02, 9, 0xff], withoutResponse: false);
      await Future.delayed(const Duration(milliseconds: 800));
      await _soundCharacteristic!
          .write([0x02, 10, 0xff], withoutResponse: false);
      setState(() {
        _statusMessage = '効果音を鳴らしました';
      });
    } catch (e) {
      setState(() {
        _statusMessage = '効果音の再生エラー: $e';
      });
    }
  }

  @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: 'playSound',
            onPressed: _playSound,
            tooltip: '効果音再生',
            child: const Icon(Icons.volume_up),
          ),
          const SizedBox(width: 16),
          FloatingActionButton(
            heroTag: 'disconnect',
            onPressed: _disconnectDevice,
            tooltip: '切断',
            child: const Icon(Icons.link_off),
          ),
        ],
      ),
      floatingActionButtonLocation: FloatingActionButtonLocation.endFloat,
    );
  }
}

アプリを動作させた結果

上記を Androidスマホで動作させ、実行した時の様子は以下のとおりです。

アプリ上の 3つのボタンをそれぞれ押した際に「toio との接続」「音の再生」「toio との接続の切断」が行えていることが分かります(アプリのボタン押下時に、それらに対応する音が toio から鳴っています)。

ChatGPT(o3-mini-high)が良い感じにコードを書いてくれたので、やりたいことをサクッと進められました。

0
1
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
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?