首次提交

This commit is contained in:
wyf
2026-04-25 17:45:10 +08:00
commit ebd4096bfc
135 changed files with 6805 additions and 0 deletions

View File

@@ -0,0 +1,373 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:web_frontend/models/DeviceInfo.dart';
import 'package:web_frontend/models/LogType.dart';
import 'package:web_frontend/models/UserInfo.dart';
import 'package:web_frontend/services/WebSocketService.dart';
class ControlPanelController extends GetxController {
final WebSocketService webSocketService = Get.find<WebSocketService>();
// 用户相关
final users = <UserInfo>[].obs;
final selectedUser = Rx<UserInfo?>(null);
// 设备相关
final devices = <DeviceInfo>[].obs;
final searchKeyword = ''.obs;
// 信号强度过滤
final rssiFilterValue = (-70).obs; // 默认-70dBm
// 日志相关
final connectionLogs = <LogEntry>[].obs;
final deviceLogs = <LogEntry>[].obs;
final allLogs = <LogEntry>[].obs;
final autoScroll = true.obs;
final ScrollController logScrollController = ScrollController();
// 命令输入
final commandController = TextEditingController();
// 日志过滤器
final showAllLogs = true.obs;
final showConnectionLogs = true.obs;
final showDeviceLogs = true.obs;
// 过滤后的设备列表(包含名称搜索和信号强度过滤)
List<DeviceInfo> get filteredDevices {
List<DeviceInfo> result = devices.toList();
// 名称/ID 模糊查询
if (searchKeyword.value.isNotEmpty) {
result = result
.where((device) =>
device.name
.toLowerCase()
.contains(searchKeyword.value.toLowerCase()) ||
device.id
.toLowerCase()
.contains(searchKeyword.value.toLowerCase()))
.toList();
}
// 信号强度过滤(只显示信号强度大于等于设定值的设备)
result =
result.where((device) => device.rssi >= rssiFilterValue.value).toList();
// 按信号强度排序(从强到弱)
result.sort((a, b) => b.rssi.compareTo(a.rssi));
return result;
}
// 获取当前显示的日志
List<LogEntry> get displayLogs {
if (showAllLogs.value) {
return allLogs.toList();
}
final logs = <LogEntry>[];
if (showConnectionLogs.value) {
logs.addAll(connectionLogs);
}
if (showDeviceLogs.value) {
logs.addAll(deviceLogs);
}
logs.sort((a, b) => a.time.compareTo(b.time));
return logs;
}
@override
void onInit() {
super.onInit();
_initWebSocketHandlers();
}
@override
void onClose() {
commandController.dispose();
logScrollController.dispose();
super.onClose();
}
void _initWebSocketHandlers() {
webSocketService.registerMessageHandler(_handleWebSocketMessage);
}
// void _handleWebSocketMessage(Map<String, dynamic> data) {
// final type = data['type'] as String?;
// switch (type) {
// case 'user_list':
// _updateUserList(data['users']);
// break;
// case 'device_selected':
// _addConnectionLog('已连接到设备: ${data['targetUuid']}', LogType.success);
// break;
// case 'device_message':
// _handleDeviceMessage(data['data']);
// break;
// case 'error':
// _addConnectionLog('错误: ${data['message']}', LogType.error);
// break;
// default:
// if (type != null) {
// _addConnectionLog('收到消息类型: $type', LogType.info);
// }
// }
// }
void _handleWebSocketMessage(Map<String, dynamic> data) {
final type = data['type'] as String?;
switch (type) {
case 'user_list':
_updateUserList(data['users']);
break;
case 'device_selected':
_addConnectionLog('已连接到设备: ${data['targetUuid']}', LogType.success);
// 设备连接成功后,自动触发扫描
_addConnectionLog('设备已就绪,自动开始扫描...', LogType.info);
webSocketService.startScan();
_addDeviceLog('自动发送扫描命令', LogType.info);
break;
case 'device_message':
_handleDeviceMessage(data['data']);
break;
case 'error':
_addConnectionLog('错误: ${data['message']}', LogType.error);
break;
default:
if (type != null) {
_addConnectionLog('收到消息类型: $type', LogType.info);
}
}
}
void _updateUserList(List<dynamic> usersData) {
users.value = usersData.map((u) => UserInfo.fromJson(u)).toList();
if (selectedUser.value != null &&
!users.any((u) => u.uuid == selectedUser.value!.uuid)) {
selectedUser.value = null;
devices.clear();
}
_addConnectionLog('设备列表更新: ${users.length} 个在线设备', LogType.info);
}
void _handleDeviceMessage(Map<String, dynamic> data) {
final deviceDataType = data['type'] as String?;
switch (deviceDataType) {
case 'device_list':
_updateDeviceList(data['devices']);
break;
case 'bluetooth_data':
_addDeviceLog('收到蓝牙数据: ${data['data']}', LogType.device);
break;
case 'bluetooth_status':
final isConnected = data['isConnected'] as bool? ?? false;
_addDeviceLog('蓝牙状态: ${isConnected ? "已连接" : "未连接"}',
isConnected ? LogType.success : LogType.warning);
break;
case 'scan_start':
_addDeviceLog('开始扫描蓝牙设备...', LogType.info);
break;
case 'scan_stop':
_addDeviceLog('停止扫描', LogType.info);
break;
default:
_addDeviceLog('设备消息: $data', LogType.device);
}
}
void _updateDeviceList(List<dynamic> devicesData) {
devices.value = devicesData.map((d) => DeviceInfo.fromJson(d)).toList();
// _addDeviceLog('发现 ${devices.length} 个蓝牙设备', LogType.success);
}
// 添加连接日志WebSocket相关
void _addConnectionLog(String message, LogType type) {
final log = LogEntry(
message: message,
time: DateTime.now(),
type: type,
);
connectionLogs.add(log);
_updateAllLogs(log);
_scrollToBottom();
// 限制日志数量
if (connectionLogs.length > 500) {
connectionLogs.removeAt(0);
}
}
// 添加设备日志
void _addDeviceLog(String message, LogType type) {
final log = LogEntry(
message: message,
time: DateTime.now(),
type: type,
deviceId: selectedUser.value?.uuid,
);
deviceLogs.add(log);
_updateAllLogs(log);
_scrollToBottom();
if (deviceLogs.length > 500) {
deviceLogs.removeAt(0);
}
}
// 添加日志(供外部调用)
void addLog(String message, LogType type) {
_addConnectionLog(message, type);
}
void _updateAllLogs(LogEntry log) {
allLogs.add(log);
if (allLogs.length > 1000) {
allLogs.removeAt(0);
}
}
void _scrollToBottom() {
if (autoScroll.value) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (logScrollController.hasClients) {
logScrollController.animateTo(
logScrollController.position.maxScrollExtent,
duration: const Duration(milliseconds: 300),
curve: Curves.easeOut,
);
}
});
}
}
// ==================== 公共方法 ====================
// 更新信号强度过滤值
void updateRssiFilter(int value) {
rssiFilterValue.value = value;
_addConnectionLog('信号强度过滤设置为: ${value} dBm', LogType.info);
}
// 重置信号强度过滤
void resetRssiFilter() {
rssiFilterValue.value = -100;
_addConnectionLog('信号强度过滤已重置', LogType.info);
}
// 选择设备并建立连接
void selectUser(UserInfo? user) {
if (user == null) return;
selectedUser.value = user;
devices.clear();
// 通过 WebSocket 选择设备,建立连接
webSocketService.selectDevice(user.uuid);
_addConnectionLog('正在连接设备: ${user.deviceModel}', LogType.info);
}
// 发送自定义数据
void sendCustomData() {
if (selectedUser.value == null) {
_addConnectionLog('请先选择设备', LogType.warning);
return;
}
final command = commandController.text.trim();
if (command.isEmpty) {
_addConnectionLog('请输入命令或数据', LogType.warning);
return;
}
webSocketService.sendDataToDevice(command);
_addDeviceLog('发送数据: $command', LogType.info);
commandController.clear();
}
// 开始扫描蓝牙设备
void sendScanCommand() {
if (selectedUser.value == null) {
_addConnectionLog('请先选择设备', LogType.warning);
return;
}
webSocketService.startScan();
_addDeviceLog('发送扫描命令', LogType.info);
}
// 停止扫描
void sendStopScanCommand() {
if (selectedUser.value == null) {
_addConnectionLog('请先选择设备', LogType.warning);
return;
}
webSocketService.stopScan();
_addDeviceLog('发送停止扫描命令', LogType.info);
}
// 连接蓝牙设备
void connectToDevice(DeviceInfo device) {
if (selectedUser.value == null) {
_addConnectionLog('请先选择设备', LogType.warning);
return;
}
webSocketService.connectToDevice(device.id);
_addDeviceLog('连接蓝牙设备: ${device.name}', LogType.info);
}
// 断开蓝牙设备
void disconnectDevice() {
if (selectedUser.value == null) {
_addConnectionLog('请先选择设备', LogType.warning);
return;
}
webSocketService.disconnectDevice();
_addDeviceLog('断开蓝牙设备连接', LogType.info);
}
// 清空日志
void clearLogs() {
allLogs.clear();
connectionLogs.clear();
deviceLogs.clear();
_addConnectionLog('日志已清空', LogType.info);
}
// 切换自动滚动
void toggleAutoScroll(bool value) {
autoScroll.value = value;
}
// 切换日志过滤器
void toggleShowAllLogs(bool value) {
showAllLogs.value = value;
}
void toggleShowConnectionLogs(bool value) {
showConnectionLogs.value = value;
}
void toggleShowDeviceLogs(bool value) {
showDeviceLogs.value = value;
}
}

33
lib/main.dart Normal file
View File

@@ -0,0 +1,33 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:web_frontend/services/WebSocketService.dart';
import 'package:web_frontend/views/ControlPanelView.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// 初始化服务
await Get.putAsync(() async => WebSocketService());
// 连接 WebSocket
Get.find<WebSocketService>().connect();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'BLE Debug Control Panel',
theme: ThemeData(
primarySwatch: Colors.blue,
useMaterial3: true,
),
home: ControlPanelView(),
);
}
}

View File

@@ -0,0 +1,20 @@
// 设备信息模型
class DeviceInfo {
final String name;
final String id;
final int rssi;
DeviceInfo({
required this.name,
required this.id,
required this.rssi,
});
factory DeviceInfo.fromJson(Map<String, dynamic> json) {
return DeviceInfo(
name: json['name'] ?? '',
id: json['id'] ?? '',
rssi: json['rssi'] ?? 0,
);
}
}

24
lib/models/LogType.dart Normal file
View File

@@ -0,0 +1,24 @@
// 日志类型
enum LogType {
info,
success,
error,
warning,
device, // 设备日志
connection, // 连接日志
}
// 日志条目模型
class LogEntry {
final String message;
final DateTime time;
final LogType type;
final String? deviceId; // 可选关联的设备ID
LogEntry({
required this.message,
required this.time,
required this.type,
this.deviceId,
});
}

22
lib/models/UserInfo.dart Normal file
View File

@@ -0,0 +1,22 @@
// 用户信息模型
class UserInfo {
final String uuid;
final String deviceModel;
final String connectedAt;
UserInfo({
required this.uuid,
required this.deviceModel,
required this.connectedAt,
});
factory UserInfo.fromJson(Map<String, dynamic> json) {
return UserInfo(
uuid: json['uuid'] ?? '',
deviceModel: json['deviceModel'] ?? '',
connectedAt: json['connectedAt'] ?? '',
);
}
}

View File

@@ -0,0 +1,197 @@
import 'dart:async';
import 'dart:convert';
import 'package:get/get.dart';
import 'package:web_socket_channel/web_socket_channel.dart';
import 'package:web_socket_channel/status.dart' as status;
class WebSocketService extends GetxService {
//webscoket地址
static const String WS_URL = 'ws://192.168.1.129:8089/ws';
WebSocketChannel? _channel;
final RxBool isConnected = false.obs;
final RxString connectionStatus = '未连接'.obs;
// 当前选中的设备UUID
final RxString selectedDeviceUuid = ''.obs;
Timer? _reconnectTimer;
int _reconnectAttempts = 0;
static const int MAX_RECONNECT_ATTEMPTS = 10;
@override
void onClose() {
disconnect();
_reconnectTimer?.cancel();
super.onClose();
}
Future<void> connect() async {
try {
_channel = WebSocketChannel.connect(Uri.parse(WS_URL));
isConnected.value = true;
connectionStatus.value = '已连接';
_reconnectAttempts = 0;
_sendMessage({
'type': 'web_register',
'clientId': 'web_client_${DateTime.now().millisecondsSinceEpoch}',
});
_channel!.stream.listen(
(message) {
_handleMessage(message);
},
onError: (error) {
print('WebSocket错误: $error');
connectionStatus.value = '错误: $error';
isConnected.value = false;
_scheduleReconnect();
},
onDone: () {
print('WebSocket连接断开');
connectionStatus.value = '已断开';
isConnected.value = false;
_scheduleReconnect();
},
);
} catch (e) {
print('WebSocket连接失败: $e');
connectionStatus.value = '连接失败';
isConnected.value = false;
_scheduleReconnect();
}
}
void _scheduleReconnect() {
if (_reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
connectionStatus.value = '重连失败,请手动刷新';
return;
}
_reconnectTimer?.cancel();
_reconnectTimer = Timer(Duration(seconds: 5), () {
_reconnectAttempts++;
connectionStatus.value = '重连中 (${_reconnectAttempts}/$MAX_RECONNECT_ATTEMPTS)...';
connect();
});
}
void disconnect() {
_channel?.sink.close(status.goingAway);
_channel = null;
isConnected.value = false;
connectionStatus.value = '已断开';
}
void _sendMessage(Map<String, dynamic> message) {
if (_channel != null && isConnected.value) {
_channel!.sink.add(jsonEncode(message));
} else {
print('WebSocket未连接无法发送消息: $message');
}
}
final List<Function(Map<String, dynamic>)> _messageHandlers = [];
void registerMessageHandler(Function(Map<String, dynamic>) handler) {
_messageHandlers.add(handler);
}
void _handleMessage(dynamic message) {
try {
final data = jsonDecode(message);
for (var handler in _messageHandlers) {
handler(data);
}
} catch (e) {
print('解析消息失败: $e');
}
}
// ==================== 公开方法 ====================
// 选择要控制的设备(建立连接)
void selectDevice(String uuid) {
selectedDeviceUuid.value = uuid;
_sendMessage({
'type': 'select_device',
'targetUuid': uuid,
'timestamp': DateTime.now().toIso8601String(),
});
print('选择设备: $uuid');
}
// 开始扫描
void startScan() {
if (selectedDeviceUuid.value.isEmpty) {
print('请先选择设备');
return;
}
_sendMessage({
'type': 'scan',
'targetUuid': selectedDeviceUuid.value,
'timestamp': DateTime.now().toIso8601String(),
});
print('发送扫描命令');
}
// 停止扫描
void stopScan() {
if (selectedDeviceUuid.value.isEmpty) {
print('请先选择设备');
return;
}
_sendMessage({
'type': 'stop_scan',
'targetUuid': selectedDeviceUuid.value,
'timestamp': DateTime.now().toIso8601String(),
});
print('发送停止扫描命令');
}
// 连接蓝牙设备
void connectToDevice(String deviceId) {
if (selectedDeviceUuid.value.isEmpty) {
print('请先选择设备');
return;
}
_sendMessage({
'type': 'connect',
'targetUuid': selectedDeviceUuid.value,
'deviceId': deviceId,
'timestamp': DateTime.now().toIso8601String(),
});
print('连接蓝牙设备: $deviceId');
}
// 断开蓝牙设备
void disconnectDevice() {
if (selectedDeviceUuid.value.isEmpty) {
print('请先选择设备');
return;
}
_sendMessage({
'type': 'disconnect',
'targetUuid': selectedDeviceUuid.value,
'timestamp': DateTime.now().toIso8601String(),
});
print('断开蓝牙设备');
}
// 发送数据到设备
void sendDataToDevice(String data) {
if (selectedDeviceUuid.value.isEmpty) {
print('请先选择设备');
return;
}
_sendMessage({
'type': 'send_data',
'targetUuid': selectedDeviceUuid.value,
'data': data,
'timestamp': DateTime.now().toIso8601String(),
});
print('发送数据: $data');
}
}

File diff suppressed because it is too large Load Diff