You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

210 lines
6.4 KiB

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:device_jieli/device_jieli.dart';
import 'package:permission_handler/permission_handler.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final _plugin = Jielihome.instance;
final _devices = <String, JieliDevice>{};
StreamSubscription<JieliEvent>? _sub;
bool _initialized = false;
bool _scanning = false;
String? _connectedAddress;
String _status = 'idle';
final _logs = <String>[];
@override
void initState() {
super.initState();
_bootstrap();
}
@override
void dispose() {
_sub?.cancel();
super.dispose();
}
Future<void> _bootstrap() async {
await [
Permission.bluetoothScan,
Permission.bluetoothConnect,
Permission.locationWhenInUse,
].request();
await _plugin.initialize(enableLog: true);
_sub = _plugin.events.listen(_onEvent);
setState(() => _initialized = true);
_log('initialized');
}
void _onEvent(JieliEvent event) {
if (event is DeviceFoundEvent) {
setState(() => _devices[event.device.address] = event.device);
} else if (event is ScanStatusEvent) {
setState(() {
_scanning = event.started;
_status = event.started ? 'scanning' : 'scan stopped';
});
_log('scanStatus started=${event.started} ble=${event.ble}');
} else if (event is ConnectionStateEvent) {
_log('connectionState ${event.address} state=${event.state}');
setState(() {
if (event.state == ConnectionStateEvent.connectionOk) {
_connectedAddress = event.address;
_status = 'connected';
} else if (event.state == ConnectionStateEvent.connectionDisconnect &&
event.address == _connectedAddress) {
_connectedAddress = null;
_status = 'disconnected';
} else if (event.state == ConnectionStateEvent.connectionConnecting) {
_status = 'connecting…';
}
});
} else if (event is RcspInitEvent) {
_log('rcspInit ${event.address} code=${event.code}');
} else if (event is AdapterStatusEvent) {
_log('adapter enabled=${event.enabled} hasBle=${event.hasBle}');
} else if (event is BondStatusEvent) {
_log('bond ${event.address} status=${event.status}');
} else if (event is UnknownJieliEvent) {
_log('unknown ${event.raw}');
}
}
void _log(String msg) {
setState(() {
_logs.insert(0, msg);
if (_logs.length > 200) _logs.removeLast();
});
}
Future<void> _toggleScan() async {
try {
if (_scanning) {
await _plugin.stopScan();
} else {
_devices.clear();
await _plugin.startScan();
}
} catch (e) {
_log('scan error: $e');
}
}
Future<void> _connect(JieliDevice d) async {
try {
await _plugin.stopScan();
setState(() => _status = 'connecting ${d.address}');
await _plugin.connect(d);
} catch (e) {
_log('connect error: $e');
}
}
Future<void> _disconnect() async {
final addr = _connectedAddress;
if (addr == null) return;
try {
await _plugin.disconnect(addr);
} catch (e) {
_log('disconnect error: $e');
}
}
@override
Widget build(BuildContext context) {
final list = _devices.values.toList()
..sort((a, b) => (b.rssi ?? -127).compareTo(a.rssi ?? -127));
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('JieLi Home Demo'),
actions: [
if (_connectedAddress != null)
IconButton(
icon: const Icon(Icons.bluetooth_disabled),
onPressed: _disconnect,
),
],
),
body: !_initialized
? const Center(child: CircularProgressIndicator())
: Column(
children: [
Padding(
padding: const EdgeInsets.all(8),
child: Row(
children: [
Expanded(
child: Text('Status: $_status'
'${_connectedAddress != null ? '\nConnected: $_connectedAddress' : ''}'),
),
ElevatedButton.icon(
onPressed: _toggleScan,
icon: Icon(_scanning ? Icons.stop : Icons.search),
label: Text(_scanning ? 'Stop' : 'Scan'),
),
],
),
),
const Divider(height: 1),
Expanded(
flex: 3,
child: ListView.separated(
itemCount: list.length,
separatorBuilder: (_, __) => const Divider(height: 1),
itemBuilder: (_, i) {
final d = list[i];
return ListTile(
dense: true,
title: Text(d.name),
subtitle: Text(
'${d.address} rssi=${d.rssi} type=${d.deviceType} way=${d.connectWay}'),
trailing: ElevatedButton(
onPressed: () => _connect(d),
child: const Text('Connect'),
),
);
},
),
),
const Divider(height: 1),
Container(
color: Colors.black87,
height: 200,
child: ListView.builder(
itemCount: _logs.length,
itemBuilder: (_, i) => Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8, vertical: 2),
child: Text(
_logs[i],
style: const TextStyle(
color: Colors.greenAccent,
fontSize: 11,
fontFamily: 'monospace'),
),
),
),
),
],
),
),
);
}
}