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.
285 lines
8.7 KiB
285 lines
8.7 KiB
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 '../../../data/services/meeting/meeting_upload_service.dart';
|
|
import 'meeting_home_controller.dart';
|
|
|
|
class MeetingConnectController extends GetxController {
|
|
final _meetingController = Get.find<MeetingHomeController>();
|
|
|
|
RxString stateStr = '搜索中...'.obs;
|
|
RxBool isRescan = false.obs;
|
|
RxString importedFilesStr = ''.obs;
|
|
|
|
late StreamSubscription<List<ScanResult>> _scanResultsSubscription;
|
|
late StreamSubscription<bool> _isScanningSubscription;
|
|
BluetoothDevice? _device;
|
|
|
|
bool isScanFailed = false;
|
|
|
|
BluetoothCharacteristic? _writeCharacteristic;
|
|
StreamSubscription<List<int>>? _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();
|
|
_stopScan();
|
|
_disconnect();
|
|
super.onClose();
|
|
}
|
|
|
|
Future<void> startScan() async {
|
|
try {
|
|
isScanFailed = false;
|
|
await FlutterBluePlus.startScan(
|
|
timeout: const Duration(seconds: 15),
|
|
withServices: [
|
|
Guid('FFF0'),
|
|
],
|
|
withNames: ['AI PEN'],
|
|
);
|
|
} catch (e) {
|
|
isScanFailed = true;
|
|
}
|
|
}
|
|
|
|
Future<void> _stopScan() async {
|
|
await FlutterBluePlus.stopScan();
|
|
}
|
|
|
|
Future<void> _connect() async {
|
|
await _device?.connect();
|
|
await _discoverServices();
|
|
}
|
|
|
|
Future<void> _disconnect() async {
|
|
_ftpConnect?.disconnect();
|
|
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<void> _discoverServices() async {
|
|
List<BluetoothService> services = await _device?.discoverServices() ?? [];
|
|
if (services.isNotEmpty) {
|
|
for (var element in services) {
|
|
if (element.serviceUuid == Guid('FFF0')) {
|
|
_readCharacteristics(element.characteristics);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Future<void> _readCharacteristics(
|
|
List<BluetoothCharacteristic> 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<void> sendData(List<int> value) async {
|
|
await _writeCharacteristic?.write(value, withoutResponse: true);
|
|
}
|
|
|
|
void _listenData(List<int> 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<FTPEntry>? 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);
|
|
}
|
|
await _disconnect();
|
|
stateStr.value = '下载完成';
|
|
importedFilesStr.value = '下载完成,共${listDirectory.length}个文件,2秒后自动返回';
|
|
Future.delayed(const Duration(seconds: 2), () {
|
|
_executeUpload();
|
|
Get.back();
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
Future<void> _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,
|
|
isUpload: false,
|
|
);
|
|
}
|
|
|
|
Future<void> _executeUpload() async {
|
|
await Future.delayed(const Duration(seconds: 3));
|
|
final meetingUploadService = Get.find<MeetingUploadService>();
|
|
meetingUploadService.executeUpload();
|
|
}
|
|
|
|
String _decodePassword(List<int> data) {
|
|
final passwordData = data.sublist(3, data.length - 2);
|
|
final decodedBytes = passwordData.map((byte) => byte ^ 0xFF).toList();
|
|
return String.fromCharCodes(decodedBytes);
|
|
}
|
|
}
|
|
|