Files
ShowenV2/clients/flutter/lib/providers/ble_provider.dart
showen d30c111c71 feat: M1.1 完成 + M1.2 启动 — 全量更新
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>
2026-03-14 18:12:42 +08:00

254 lines
7.7 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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();
}
}