M1.1 收尾: - 24项 P0/P1/P2 bug 修复 (Rust 107 tests + Flutter 15 tests) - Flutter App v0.3: cupertino_icons 修复, 单元测试, 调试面板, APK 52.6MB - 示例插件完善: manifest.json + 请求/响应示范 + 7个测试 - API 文档重写 (以 routes.rs 为唯一权威) - MILESTONES.md 更新至 100% M1.2 启动: - P0: 插件管理 API 闭环 (handle_manager_message Custom 分支 + broadcast_plugin_states) - ServiceManager 集成测试 8/8 (tests/m1_2_service_manager.rs) - M1.2 测试计划 (docs/M1.2_TEST_PLAN.md, 18个E2E场景) - 动态插件系统: auto_rollback + version_manager GC + 路径穿越防护 总计: Rust 115/115 测试, Flutter 15/15 测试, 零 warning Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
254 lines
7.7 KiB
Dart
254 lines
7.7 KiB
Dart
import 'dart:async';
|
||
|
||
import 'package:flutter/foundation.dart';
|
||
|
||
import '../models/ble_models.dart';
|
||
import '../services/ble_service.dart';
|
||
import 'debug_provider.dart';
|
||
|
||
class BleProvider extends ChangeNotifier {
|
||
BleProvider({BleService? bleService, required DebugProvider debugProvider})
|
||
: _bleService = bleService ?? BleService(),
|
||
_debugProvider = debugProvider;
|
||
|
||
final BleService _bleService;
|
||
final DebugProvider _debugProvider;
|
||
|
||
StreamSubscription<List<BleDevice>>? _scanSubscription;
|
||
StreamSubscription<BleStatus>? _statusSubscription;
|
||
|
||
List<BleDevice> _devices = const <BleDevice>[];
|
||
BleDevice? _selectedDevice;
|
||
BleStatus? _latestStatus;
|
||
ProvisioningState _provisioningState = ProvisioningState.scanning;
|
||
String? _errorMessage;
|
||
bool _isScanning = false;
|
||
bool _isConnecting = false;
|
||
bool _isProvisioning = false;
|
||
bool _isSendingCommand = false;
|
||
bool _isConnected = false;
|
||
bool _isDisposed = false;
|
||
|
||
List<BleDevice> get devices => _devices;
|
||
BleDevice? get selectedDevice => _selectedDevice;
|
||
BleStatus? get latestStatus => _latestStatus;
|
||
ProvisioningState get provisioningState => _provisioningState;
|
||
String? get errorMessage => _errorMessage;
|
||
bool get isScanning => _isScanning;
|
||
bool get isConnecting => _isConnecting;
|
||
bool get isProvisioning => _isProvisioning;
|
||
bool get isSendingCommand => _isSendingCommand;
|
||
bool get isConnected => _isConnected;
|
||
|
||
Future<void> startScan() async {
|
||
_debugProvider.addBleLog('Start BLE scan');
|
||
_errorMessage = null;
|
||
_selectedDevice = null;
|
||
_isConnected = false;
|
||
_provisioningState = ProvisioningState.scanning;
|
||
_isScanning = true;
|
||
_notifySafely();
|
||
|
||
await _scanSubscription?.cancel();
|
||
_scanSubscription = _bleService
|
||
.scanForShowenDevices()
|
||
.listen((List<BleDevice> scannedDevices) {
|
||
_devices = scannedDevices;
|
||
_debugProvider.addBleLog(
|
||
'BLE scan update (${scannedDevices.length} devices)',
|
||
);
|
||
_notifySafely();
|
||
}, onError: (Object error, StackTrace stackTrace) {
|
||
_errorMessage = error.toString();
|
||
_isScanning = false;
|
||
_provisioningState = ProvisioningState.failed;
|
||
_debugProvider.addBleLog('BLE scan failed', details: error);
|
||
_notifySafely();
|
||
});
|
||
|
||
Future<void>.delayed(const Duration(seconds: 6), () {
|
||
if (_isScanning) {
|
||
_isScanning = false;
|
||
_debugProvider.addBleLog('BLE scan completed');
|
||
_notifySafely();
|
||
}
|
||
});
|
||
}
|
||
|
||
Future<void> connectToDevice(BleDevice device) async {
|
||
_debugProvider.addBleLog(
|
||
'Connect BLE device ${device.name.isNotEmpty ? device.name : device.id}',
|
||
);
|
||
_selectedDevice = device;
|
||
_errorMessage = null;
|
||
_isConnecting = true;
|
||
_isScanning = false;
|
||
_provisioningState = ProvisioningState.connecting;
|
||
_notifySafely();
|
||
|
||
try {
|
||
await _bleService.connectToDevice(device);
|
||
await _subscribeToStatus();
|
||
_isConnected = true;
|
||
_debugProvider.addBleLog('BLE device connected');
|
||
} catch (error) {
|
||
_isConnected = false;
|
||
_errorMessage = error.toString();
|
||
_provisioningState = ProvisioningState.failed;
|
||
_debugProvider.addBleLog('BLE connect failed', details: error);
|
||
rethrow;
|
||
} finally {
|
||
_isConnecting = false;
|
||
_notifySafely();
|
||
}
|
||
}
|
||
|
||
Future<void> provisionWifi(String ssid, String password) async {
|
||
_debugProvider.addBleLog(
|
||
'Provision WiFi over BLE',
|
||
details: <String, Object>{'ssid': ssid},
|
||
);
|
||
_errorMessage = null;
|
||
_latestStatus = null;
|
||
_isProvisioning = true;
|
||
_provisioningState = ProvisioningState.writingCredentials;
|
||
_notifySafely();
|
||
|
||
try {
|
||
final Future<BleStatus> operation = _bleService.provisionWifi(
|
||
ssid,
|
||
password,
|
||
timeout: const Duration(seconds: 30),
|
||
);
|
||
|
||
Future<void>.delayed(const Duration(milliseconds: 400), () {
|
||
if (_isProvisioning &&
|
||
_provisioningState == ProvisioningState.writingCredentials) {
|
||
_provisioningState = ProvisioningState.connectingWifi;
|
||
_notifySafely();
|
||
}
|
||
});
|
||
|
||
final BleStatus result = await operation;
|
||
_latestStatus = result;
|
||
_provisioningState = result.ok
|
||
? ProvisioningState.success
|
||
: ProvisioningState.failed;
|
||
if (!result.ok) {
|
||
_errorMessage = result.error ?? 'WiFi provisioning failed';
|
||
_debugProvider.addBleLog('BLE provisioning returned failure', details: result.error);
|
||
} else {
|
||
_debugProvider.addBleLog('BLE provisioning succeeded');
|
||
}
|
||
} on TimeoutException {
|
||
_errorMessage = 'BLE 配网超时(30 秒)';
|
||
_provisioningState = ProvisioningState.failed;
|
||
_debugProvider.addBleLog('BLE provisioning timed out');
|
||
rethrow;
|
||
} catch (error) {
|
||
_errorMessage = error.toString();
|
||
_provisioningState = ProvisioningState.failed;
|
||
_debugProvider.addBleLog('BLE provisioning failed', details: error);
|
||
rethrow;
|
||
} finally {
|
||
_isProvisioning = false;
|
||
_notifySafely();
|
||
}
|
||
}
|
||
|
||
Future<void> disconnect() async {
|
||
await _scanSubscription?.cancel();
|
||
await _statusSubscription?.cancel();
|
||
_scanSubscription = null;
|
||
_statusSubscription = null;
|
||
await _bleService.disconnect();
|
||
_isConnected = false;
|
||
_isConnecting = false;
|
||
_isProvisioning = false;
|
||
_selectedDevice = null;
|
||
_debugProvider.addBleLog('BLE disconnected');
|
||
_notifySafely();
|
||
}
|
||
|
||
Future<void> sendCommand(String command) async {
|
||
_debugProvider.addBleLog('Send BLE command', details: command);
|
||
_errorMessage = null;
|
||
_isSendingCommand = true;
|
||
_notifySafely();
|
||
|
||
try {
|
||
await _bleService.sendCommand(command);
|
||
_debugProvider.addBleLog('BLE command sent', details: command);
|
||
} catch (error) {
|
||
_errorMessage = error.toString();
|
||
_debugProvider.addBleLog('BLE command failed', details: error);
|
||
rethrow;
|
||
} finally {
|
||
_isSendingCommand = false;
|
||
_notifySafely();
|
||
}
|
||
}
|
||
|
||
Future<void> retryScan() async {
|
||
_debugProvider.addBleLog('Retry BLE scan');
|
||
await disconnect();
|
||
_devices = const <BleDevice>[];
|
||
_latestStatus = null;
|
||
_errorMessage = null;
|
||
_provisioningState = ProvisioningState.scanning;
|
||
_notifySafely();
|
||
await startScan();
|
||
}
|
||
|
||
Future<void> _subscribeToStatus() async {
|
||
await _statusSubscription?.cancel();
|
||
final Stream<BleStatus> stream = await _bleService.subscribeToStatus();
|
||
_statusSubscription = stream.listen((BleStatus status) {
|
||
_latestStatus = status;
|
||
_debugProvider.addBleLog(
|
||
'BLE status update',
|
||
details: <String, Object?>{
|
||
'ok': status.ok,
|
||
'action': status.action,
|
||
'state': status.state,
|
||
'error': status.error,
|
||
},
|
||
);
|
||
if (!status.ok) {
|
||
_errorMessage = status.error ?? 'BLE status returned an error';
|
||
}
|
||
if (status.action == 'connect') {
|
||
if (!status.ok) {
|
||
_provisioningState = ProvisioningState.failed;
|
||
} else if (!status.isQueued) {
|
||
_provisioningState = ProvisioningState.success;
|
||
} else if (_isProvisioning) {
|
||
_provisioningState = ProvisioningState.connectingWifi;
|
||
}
|
||
}
|
||
_notifySafely();
|
||
}, onError: (Object error, StackTrace stackTrace) {
|
||
_errorMessage = error.toString();
|
||
_provisioningState = ProvisioningState.failed;
|
||
_debugProvider.addBleLog('BLE status stream failed', details: error);
|
||
_notifySafely();
|
||
});
|
||
}
|
||
|
||
@override
|
||
void dispose() {
|
||
_isDisposed = true;
|
||
unawaited(_scanSubscription?.cancel());
|
||
unawaited(_statusSubscription?.cancel());
|
||
unawaited(_bleService.dispose());
|
||
super.dispose();
|
||
}
|
||
|
||
void _notifySafely() {
|
||
if (_isDisposed) {
|
||
return;
|
||
}
|
||
notifyListeners();
|
||
}
|
||
}
|