import 'dart:async'; import 'dart:io'; import 'package:flutter_blue_plus/flutter_blue_plus.dart'; import 'package:get/get.dart'; import 'package:path_provider/path_provider.dart'; import 'package:wifi_iot/wifi_iot.dart'; import '../../../core/utils/ftpconnect/ftpconnect.dart'; import 'meeting_controller.dart'; class MeetingConnectController extends GetxController { final _meetingController = Get.find(); RxString stateStr = '搜索中...'.obs; RxBool isRescan = false.obs; RxString importedFilesStr = ''.obs; late StreamSubscription> _scanResultsSubscription; late StreamSubscription _isScanningSubscription; BluetoothDevice? _device; bool isScanFailed = false; BluetoothCharacteristic? _writeCharacteristic; StreamSubscription>? _valueReceivedSubscription; String _wifiSSID = ''; String _wifiPassword = ''; String _ftpAddress = ''; String _ftpPort = ''; String _ftpAccount = ''; String _ftpPassword = ''; FTPConnect? _ftpConnect; RxBool isDownload = false.obs; RxDouble downloadProgress = 0.0.obs; RxInt totalReceived = 0.obs; RxInt fileSize = 0.obs; @override void onInit() { super.onInit(); _scanResultsSubscription = FlutterBluePlus.scanResults.listen((results) { for (ScanResult item in results) { _handlerScanResult(item); } }); _isScanningSubscription = FlutterBluePlus.isScanning.listen((state) { if (isScanFailed) { stateStr.value = '搜索失败,请检查蓝牙是否开启'; isRescan.value = true; return; } if (!state && _device == null) { stateStr.value = '未搜索到 AI PEN 设备'; isRescan.value = true; } else { stateStr.value = '搜索中...'; isRescan.value = false; } }); startScan(); } @override void onClose() { _scanResultsSubscription.cancel(); _isScanningSubscription.cancel(); _valueReceivedSubscription?.cancel(); _ftpConnect?.disconnect(); _stopScan(); _disconnect(); super.onClose(); } Future startScan() async { try { isScanFailed = false; await FlutterBluePlus.startScan( timeout: const Duration(seconds: 15), withServices: [ Guid('FFF0'), ], withNames: ['AI PEN'], ); } catch (e) { isScanFailed = true; } } Future _stopScan() async { await FlutterBluePlus.stopScan(); } Future _connect() async { await _device?.connect(); await _discoverServices(); } Future _disconnect() async { await WiFiForIoTPlugin.forceWifiUsage(false); await WiFiForIoTPlugin.disconnect(); await _device?.disconnect(); } void _handlerScanResult(ScanResult result) async { if (result.device.platformName == 'AI PEN') { _device = result.device; await _stopScan(); await _connect(); stateStr.value = '已连接到 AI PEN 设备'; } } Future _discoverServices() async { List services = await _device?.discoverServices() ?? []; if (services.isNotEmpty) { for (var element in services) { if (element.serviceUuid == Guid('FFF0')) { _readCharacteristics(element.characteristics); } } } } Future _readCharacteristics( List characteristics) async { for (var element in characteristics) { if (element.characteristicUuid == Guid('FFF1')) { _writeCharacteristic = element; stateStr.value = '检查是否有新文件需要导入'; sendData([0x5a, 0x01, 0x0d]); } else if (element.characteristicUuid == Guid('FFF2')) { await element.setNotifyValue(true); _valueReceivedSubscription = element.onValueReceived.listen((value) { _listenData(value); }); } } } Future sendData(List value) async { await _writeCharacteristic?.write(value, withoutResponse: true); } void _listenData(List value) { int command = value[2]; switch (command) { case 0x0d: // 是否有导入文件 if (value[3] == 0x01) { stateStr.value = '正在开启Wi-Fi...'; importedFilesStr.value = '检查到有新文件,正在准备导入'; sendData([0x5a, 0x02, 0x87, 0x01]); } else { importedFilesStr.value = '没有新文件需要导入'; } break; case 0x87: // Wi-Fi 开启/关闭 if (value[3] == 0x01 && value[4] == 0x01) { sendData([0x5a, 0x01, 0x0b]); } break; case 0x0b: // WiFi 热点 SSID _wifiSSID = String.fromCharCodes(value.sublist(3, value.length - 2)); sendData([0x5a, 0x01, 0x0c]); break; case 0x0c: // WiFi 热点密码 _wifiPassword = _decodePassword(value); _connectWifi(); break; case 0x03: // FTP 地址 _ftpAddress = String.fromCharCodes(value.sublist(3, value.length - 2)); sendData([0x5a, 0x01, 0x04]); break; case 0x04: // FTP 端口 _ftpPort = String.fromCharCodes(value.sublist(3, value.length - 2)); sendData([0x5a, 0x01, 0x05]); break; case 0x05: // FTP 账号 _ftpAccount = String.fromCharCodes(value.sublist(3, value.length - 2)); sendData([0x5a, 0x01, 0x06]); break; case 0x06: // FTP 密码 _ftpPassword = _decodePassword(value); _connectFtp(); break; default: } } void _connectWifi() async { if (_wifiSSID.isNotEmpty && _wifiPassword.isNotEmpty) { stateStr.value = '正在连接 Wi-Fi...'; await WiFiForIoTPlugin.connect( _wifiSSID, password: _wifiPassword, security: NetworkSecurity.WPA, ); await WiFiForIoTPlugin.forceWifiUsage(true); stateStr.value = 'Wi-Fi 连接成功'; sendData([0x5a, 0x01, 0x03]); } } void _connectFtp() async { if (_ftpAddress.isNotEmpty && _ftpPort.isNotEmpty && _ftpAccount.isNotEmpty && _ftpPassword.isNotEmpty) { _ftpConnect = FTPConnect( _ftpAddress, port: int.tryParse(_ftpPort) ?? 21, user: _ftpAccount, pass: _ftpPassword, ); await _ftpConnect?.connect(); _ftpConnect?.listCommand = ListCommand.LIST; List? listDirectory = await _ftpConnect?.listDirectoryContent(); if (listDirectory != null && listDirectory.isNotEmpty) { stateStr.value = '正在下载文件...'; isDownload.value = true; int totalSize = listDirectory.fold( 0, (sum, entry) => sum + (entry.size ?? 0), ); for (var i = 0; i < listDirectory.length; i++) { importedFilesStr.value = '共${listDirectory.length}个文件,正在下载第${i + 1}个文件'; int previousSize = i > 0 ? listDirectory[i - 1].size ?? 0 : 0; FTPEntry element = listDirectory[i]; await _downloadFile(totalSize, previousSize, element); await _ftpConnect?.deleteFile(element.name); } stateStr.value = '下载完成'; importedFilesStr.value = '下载完成,共${listDirectory.length}个文件,2秒后自动返回'; _disconnect(); Future.delayed(const Duration(seconds: 2), () { Get.back(); }); } } } Future _downloadFile( int totalSize, int previousSize, FTPEntry entry) async { final directory = await getApplicationDocumentsDirectory(); final directoryDir = Directory('${directory.path}/RecordingPen'); if (!await directoryDir.exists()) { await directoryDir.create(recursive: true); } final String filePath = '${directory.path}/RecordingPen/${entry.name}'; final File localFile = File(filePath); await _ftpConnect?.downloadFileWithRetry( entry.name, localFile, pRetryCount: 3, onProgress: (double progress, int received, int size) { downloadProgress.value = (previousSize + received) / totalSize; totalReceived.value = received; fileSize.value = size; }, ); await _meetingController.insertMeetingAudio(entry.name, filePath, ''); } String _decodePassword(List data) { final passwordData = data.sublist(3, data.length - 2); final decodedBytes = passwordData.map((byte) => byte ^ 0xFF).toList(); return String.fromCharCodes(decodedBytes); } }