Once you have flutter installed and configured. You can try to create your first project. Following code demonstrates how to establish connection with OP-BT/BTS Bluetooth Optical Probes and retrive its battery voltage.
Code Features
- Scan bluetooth devices nearby
- Eastablish connection with chosen device
- Communicate with optical probe and retrieve its battery voltage
- Display battery voltage
Librarires
- Bluetooth Communication: flutter_blue_plus
- Permission: permission_handler
Communication Protocal
OP-BT/BTS communicate by JSON:
- Enable command mode: {“AtCommandMode”:true}
- Retreive battery voltage: {“BatteryVoltage”:"?"}
- Exit command mode: {“AtCommandMode”:false}
Device responde battery volate in JSON format: {“BatteryVoltage”:3800}
UUID
- Service UUID:“18F0”
- Notification UUID:“2AF0”
- Writing UUID:“2AF1”
1. Create a new project
flutter create opbt_battery_voltage
cd opbt_battery_voltage
Please note, this step may take a few minutes to a few hours, depends on if you already have dependent library installed.
2. add following to pubspec.yaml:
Find the location of dependencies:
dependencies:
flutter:
sdk: flutter
Add following:
flutter_blue_plus: ^1.31.13
permission_handler: ^11.3.0
After adding dependencies, it should appear to be following:
dependencies:
flutter:
sdk: flutter
flutter_blue_plus: ^1.31.13
permission_handler: ^11.3.0
3. Modify Android Permission
opbt_battery_voltage/android/app/src/main/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" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<application>
<!-- ... 其他配置 ... -->
</application>
</manifest>
4. Copy following code to your project
opbt_battery_voltage/lib/main.dart
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
import 'package:permission_handler/permission_handler.dart';
/// Application entry point
void main() {
runApp(const MyApp());
}
/// Root application component
/// Sets the application theme and home page
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'OPBT Battery Voltage Reader',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const BatteryVoltageScreen(), // Set home page to battery voltage reading screen
);
}
}
/// Battery voltage reading screen
/// Used to scan, connect to Bluetooth devices and read battery voltage
class BatteryVoltageScreen extends StatefulWidget {
const BatteryVoltageScreen({super.key});
@override
State<BatteryVoltageScreen> createState() => _BatteryVoltageScreenState();
}
class _BatteryVoltageScreenState extends State<BatteryVoltageScreen> {
// Stores the list of scanned Bluetooth devices
List<ScanResult> scanResults = [];
// Currently connected Bluetooth device
BluetoothDevice? connectedDevice;
// Battery voltage display text
String batteryVoltage = "Not read";
// Whether device scanning is in progress
bool isScanning = false;
// Bluetooth UUID constants for OPBT devices
// These UUIDs are device-specific and need to be configured according to the actual device
final String SERVICE_UUID = "18F0"; // Service UUID
final String CHARACTERISTIC_UUID_NOTIFY = "2AF0"; // Notification characteristic UUID
final String CHARACTERISTIC_UUID_WRITE = "2AF1"; // Write characteristic UUID
@override
void initState() {
super.initState();
// Request necessary permissions during initialization
_requestPermissions();
}
/// Request Bluetooth and location permissions
/// These permissions are required for Bluetooth scanning and connection
Future<void> _requestPermissions() async {
await Permission.bluetooth.request();
await Permission.bluetoothScan.request();
await Permission.bluetoothConnect.request();
await Permission.location.request();
}
/// Start scanning for Bluetooth devices
/// Uses flutter_blue_plus library to scan nearby Bluetooth devices
void startScan() async {
setState(() {
scanResults.clear();
isScanning = true;
});
// Ensure Bluetooth adapter is ready
await FlutterBluePlus.adapterState.first;
// Start scanning with 4-second timeout
await FlutterBluePlus.startScan(timeout: const Duration(seconds: 4));
// Listen for scan results
FlutterBluePlus.scanResults.listen((results) {
setState(() {
scanResults = results;
});
});
// Listen for scan status
FlutterBluePlus.isScanning.listen((scanning) {
setState(() {
isScanning = scanning;
});
});
}
/// Connect to selected Bluetooth device
/// @param device Bluetooth device to connect to
Future<void> connectToDevice(BluetoothDevice device) async {
try {
// Connect to device
await device.connect();
setState(() {
connectedDevice = device;
});
// Read battery voltage after successful connection
await readBatteryVoltage(device);
} catch (e) {
print('Connection failed: $e');
}
}
/// Read device battery voltage
/// Interacts with device via BLE communication protocol to get battery voltage information
/// @param device Connected Bluetooth device
Future<void> readBatteryVoltage(BluetoothDevice device) async {
try {
// Get list of services provided by the device
List<BluetoothService> services = await device.discoverServices();
// Find target service
var service = services.firstWhere(
(s) => s.uuid.toString().toUpperCase().contains(SERVICE_UUID)
);
// Get write characteristic
var writeCharacteristic = service.characteristics.firstWhere(
(c) => c.uuid.toString().toUpperCase().contains(CHARACTERISTIC_UUID_WRITE)
);
// Get notification characteristic
var notifyCharacteristic = service.characteristics.firstWhere(
(c) => c.uuid.toString().toUpperCase().contains(CHARACTERISTIC_UUID_NOTIFY)
);
// Set up notification listener to receive data from device
await notifyCharacteristic.setNotifyValue(true);
notifyCharacteristic.value.listen((value) {
if (value.isNotEmpty) {
String response = String.fromCharCodes(value);
print('Received: $response');
_parseBatteryVoltage(response);
}
});
// Step 1: Enter command mode
print('Sending: {"AtCommandMode":true}');
await writeCharacteristic.write(utf8.encode('{"AtCommandMode":true}\r\n'));
await Future.delayed(const Duration(milliseconds: 1000));
// Step 2: Query battery voltage
print('Sending: {"BatteryVoltage":"?"}');
await writeCharacteristic.write(utf8.encode('{"BatteryVoltage":"?"}\r\n'));
await Future.delayed(const Duration(milliseconds: 1000));
// Step 3: Try alternative command format
print('Sending: {"GetBatteryVoltage":true}');
await writeCharacteristic.write(utf8.encode('{"GetBatteryVoltage":true}\r\n'));
await Future.delayed(const Duration(milliseconds: 1000));
// Step 4: Exit command mode
print('Sending: {"AtCommandMode":false}');
await writeCharacteristic.write(utf8.encode('{"AtCommandMode":false}\r\n'));
} catch (e) {
print('Failed to read battery voltage: $e');
}
}
/// Parse battery voltage data returned from device
/// @param response JSON-formatted string returned from device
void _parseBatteryVoltage(String response) {
try {
print('Attempting to parse: $response');
// Parse response into JSON object
Map<String, dynamic> data = json.decode(response);
// Check if it contains battery voltage field
if (data.containsKey('BatteryVoltage')) {
int voltage = data['BatteryVoltage'];
print('Parsing successful, battery voltage: $voltage mV');
setState(() {
// Update UI display, showing both millivolts and volts
batteryVoltage = '${voltage}mV (${(voltage/1000).toStringAsFixed(2)}V)';
});
} else {
print('Response does not contain BatteryVoltage field');
}
} catch (e) {
print('Failed to parse battery voltage: $e');
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('OPBT Battery Voltage Reader'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Display battery voltage
Text('Battery Voltage: $batteryVoltage'),
const SizedBox(height: 20),
// Display connection status
if (connectedDevice != null)
Text('Connected device: ${connectedDevice!.name}')
else
const Text('No device connected'),
const SizedBox(height: 20),
// Scan button
ElevatedButton(
onPressed: isScanning ? null : startScan,
child: Text(isScanning ? 'Scanning...' : 'Scan Devices'),
),
const SizedBox(height: 20),
// Device list
Expanded(
child: ListView.builder(
itemCount: scanResults.length,
itemBuilder: (context, index) {
ScanResult result = scanResults[index];
return ListTile(
title: Text(result.device.name.isEmpty
? 'Unknown Device'
: result.device.name),
subtitle: Text(result.device.id.toString()),
onTap: () => connectToDevice(result.device),
);
},
),
),
],
),
),
);
}
}
Run
Connect your phone with computer by USB cable, you’ll see a debbuging permission request. As following screen shot:
Then, run following command from powershell:
flutter run -v