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>? _scanSubscription; StreamSubscription? _statusSubscription; List _devices = const []; 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 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 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 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.delayed(const Duration(seconds: 6), () { if (_isScanning) { _isScanning = false; _debugProvider.addBleLog('BLE scan completed'); _notifySafely(); } }); } Future 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 provisionWifi(String ssid, String password) async { _debugProvider.addBleLog( 'Provision WiFi over BLE', details: {'ssid': ssid}, ); _errorMessage = null; _latestStatus = null; _isProvisioning = true; _provisioningState = ProvisioningState.writingCredentials; _notifySafely(); try { final Future operation = _bleService.provisionWifi( ssid, password, timeout: const Duration(seconds: 30), ); Future.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 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 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 retryScan() async { _debugProvider.addBleLog('Retry BLE scan'); await disconnect(); _devices = const []; _latestStatus = null; _errorMessage = null; _provisioningState = ProvisioningState.scanning; _notifySafely(); await startScan(); } Future _subscribeToStatus() async { await _statusSubscription?.cancel(); final Stream stream = await _bleService.subscribeToStatus(); _statusSubscription = stream.listen((BleStatus status) { _latestStatus = status; _debugProvider.addBleLog( 'BLE status update', details: { '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(); } }