はじめに
最近、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
コード
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)が良い感じにコードを書いてくれたので、やりたいことをサクッと進められました。