Add packet handling and logging functionality for Bluetooth data transfer

This commit is contained in:
2025-03-16 22:16:22 +08:00
parent a7700eb915
commit c68216031d
10 changed files with 782 additions and 36 deletions

View File

@ -39,6 +39,7 @@ class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp.router(
debugShowCheckedModeBanner: false,
routerConfig: routerConfig,
theme: ThemeData.light().copyWith(
materialTapTargetSize: MaterialTapTargetSize.padded,

View File

@ -0,0 +1,5 @@
/// 辅助函数将数字转换为格式化的十六进制字符串带0x前缀且至少两位数
String toHexString(int value) {
String hex = value.toRadixString(16).padLeft(2, '0');
return '0x$hex';
}

View File

@ -0,0 +1,230 @@
import 'package:logger/logger.dart';
import 'package:logger/logger.dart' as log_show;
class logger {
static void info(
[dynamic arg1,
dynamic arg2,
dynamic arg3,
dynamic arg4,
dynamic arg5,
dynamic arg6,
dynamic arg7,
dynamic arg8,
dynamic arg9,
dynamic arg10]) {
var formattedText =
getString(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10);
_logger.i(formattedText);
}
static void i(
[dynamic arg1,
dynamic arg2,
dynamic arg3,
dynamic arg4,
dynamic arg5,
dynamic arg6,
dynamic arg7,
dynamic arg8,
dynamic arg9,
dynamic arg10]) {
var formattedText =
getString(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10);
_loggerSm.i(formattedText);
}
static void w(
[dynamic arg1,
dynamic arg2,
dynamic arg3,
dynamic arg4,
dynamic arg5,
dynamic arg6,
dynamic arg7,
dynamic arg8,
dynamic arg9,
dynamic arg10]) {
var formattedText =
getString(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10);
_loggerSm.w(formattedText);
}
static void e(
[dynamic arg1,
dynamic arg2,
dynamic arg3,
dynamic arg4,
dynamic arg5,
dynamic arg6,
dynamic arg7,
dynamic arg8,
dynamic arg9,
dynamic arg10]) {
var formattedText =
getString(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10);
_loggerSm.e(formattedText);
}
static void s(
[dynamic arg1,
dynamic arg2,
dynamic arg3,
dynamic arg4,
dynamic arg5,
dynamic arg6,
dynamic arg7,
dynamic arg8,
dynamic arg9,
dynamic arg10]) {
var formattedText =
getString(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10);
loggerSuccess.i(formattedText);
}
static void error(
[dynamic arg1,
dynamic arg2,
dynamic arg3,
dynamic arg4,
dynamic arg5,
dynamic arg6,
dynamic arg7,
dynamic arg8,
dynamic arg9,
dynamic arg10]) {
var formattedText =
getString(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10);
_logger.e(formattedText);
}
static void warn(
[dynamic arg1,
dynamic arg2,
dynamic arg3,
dynamic arg4,
dynamic arg5,
dynamic arg6,
dynamic arg7,
dynamic arg8,
dynamic arg9,
dynamic arg10]) {
var formattedText =
getString(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10);
_logger.w(formattedText);
}
static void debug(
[dynamic arg1,
dynamic arg2,
dynamic arg3,
dynamic arg4,
dynamic arg5,
dynamic arg6,
dynamic arg7,
dynamic arg8,
dynamic arg9,
dynamic arg10]) {
var formattedText =
getString(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10);
_logger.d(formattedText);
}
static void success(
[dynamic arg1,
dynamic arg2,
dynamic arg3,
dynamic arg4,
dynamic arg5,
dynamic arg6,
dynamic arg7,
dynamic arg8,
dynamic arg9,
dynamic arg10]) {
var formattedText =
getString(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10);
loggerSuccess.i(formattedText);
}
static dynamic getString(
[dynamic arg1,
dynamic arg2,
dynamic arg3,
dynamic arg4,
dynamic arg5,
dynamic arg6,
dynamic arg7,
dynamic arg8,
dynamic arg9,
dynamic arg10]) {
List<dynamic> args = [
arg1,
arg2,
arg3,
arg4,
arg5,
arg6,
arg7,
arg8,
arg9,
arg10
];
// 过滤掉 null
args.removeWhere((element) => element == null);
if (args.isEmpty) return "";
if (args.length == 1) return args[0];
String formattedText = "";
for (var arg in args) {
int index = args.indexOf(arg);
if (arg == null) {
break;
}
// 如果是最后一个不加 \n
if (index == args.length - 1) {
formattedText += "$arg";
} else {
formattedText += "$arg\n";
}
}
return formattedText;
}
}
var _logger = log_show.Logger(
printer: PrettyPrinter(
stackTraceBeginIndex: 1,
methodCount: 3,
dateTimeFormat: DateTimeFormat.dateAndTime,
), // SimplePrinter PrefixPrinter LogfmtPrinter HybridPrinter PrettyPrinter
);
var _loggerSm = log_show.Logger(
printer: PrettyPrinter(
stackTraceBeginIndex: 0,
methodCount: 0,
// dateTimeFormat: DateTimeFormat.dateAndTime,
), // SimplePrinter PrefixPrinter LogfmtPrinter HybridPrinter PrettyPrinter
);
var loggerSuccess = log_show.Logger(
printer: PrettyPrinter(
stackTraceBeginIndex: 1,
methodCount: 3,
dateTimeFormat: DateTimeFormat.dateAndTime,
levelColors: {
Level.trace: AnsiColor.fg(AnsiColor.grey(0.5)),
Level.debug: const AnsiColor.none(),
Level.info: const AnsiColor.fg(41),
Level.warning: const AnsiColor.fg(208),
Level.error: const AnsiColor.fg(196),
Level.fatal: const AnsiColor.fg(199),
},
), // SimplePrinter PrefixPrinter LogfmtPrinter HybridPrinter PrettyPrinter
);

View File

@ -0,0 +1,18 @@
// 使用crc16
import 'dart:typed_data';
int crc16(Uint8List data) {
int crc = 0;
for (int byte in data) {
crc = (crc ^ byte) & 0xFFFF;
for (int i = 0; i < 8; i++) {
if ((crc & 0x0001) != 0) {
crc = ((crc >> 1) ^ 0xA001) & 0xFFFF;
} else {
crc = (crc >> 1) & 0xFFFF;
}
}
}
return crc;
}

View File

@ -0,0 +1,112 @@
// 包结构
import 'dart:typed_data';
import 'crc.dart';
class Packet {
PacketHeader header; // 包头
Uint8List data; // 数据
int crc; // 2字节CRC校验码
Packet(this.header, this.data, this.crc);
PacketType get type => header.type;
/// 将数据包序列化为字节数组(包头+数据+CRC
Uint8List toBytes() {
// 创建包头字节5字节
Uint8List headerBytes = Uint8List(5);
headerBytes[0] = header.type.value; // 类型
headerBytes[1] = (header.seqNum >> 8) & 0xFF; // 序列号高字节
headerBytes[2] = header.seqNum & 0xFF; // 序列号低字节
headerBytes[3] = (header.dataLen >> 8) & 0xFF; // 数据长度高字节
headerBytes[4] = header.dataLen & 0xFF; // 数据长度低字节
// 创建CRC字节2字节
Uint8List crcBytes = Uint8List(2);
crcBytes[0] = (crc >> 8) & 0xFF; // CRC高字节
crcBytes[1] = crc & 0xFF; // CRC低字节
// 合并所有部分:包头 + 数据 + CRC
return Uint8List.fromList([...headerBytes, ...data, ...crcBytes]);
}
@override
String toString() {
// 包类型、序列号、数据长度、CRC
return 'Packet{type=${header.type}, seqNum=${header.seqNum}, dataLen=${header.dataLen}, crc=${crc.toRadixString(16)},\nallData=${toBytes().map((e) => e.toRadixString(16)).join(",")}}';
}
}
// 包头结构
class PacketHeader {
PacketType type; // 1字节包类型
int seqNum; // 2字节序列号
int dataLen; // 2字节数据长度
PacketHeader(this.type, this.seqNum, this.dataLen);
}
/// 数据包类型
enum PacketType {
// 用于传输开始时发送,包含文件大小和分块大小等信息
start(0x01, '开始包'),
// 用于传输实际的文件数据块
data(0x02, '数据包'),
// 用于确认某个数据包已正确接收
ack(0x03, '确认包'),
// 用于指示某个数据包接收失败,请求重传
nak(0x04, '重传请求包'),
// 用于标识文件传输结束
end(0x05, '结束包'),
// 错误
error(0x06, '错误包'),
;
const PacketType(this.value, this.label);
final int value;
final String label;
static PacketType fromValue(int value) {
switch (value) {
case 0x01:
return start;
case 0x02:
return data;
case 0x03:
return ack;
case 0x04:
return nak;
case 0x05:
return end;
case 0x06:
return error;
// 默认为error
default:
return error;
}
}
}
// 打包函数
Packet packPacket(PacketType type, int seqNum, Uint8List? data) {
int dataLen = data?.length ?? 0;
PacketHeader header = PacketHeader(type, seqNum, dataLen);
Uint8List packetData = data ?? Uint8List(0);
// 创建包头字节5字节
Uint8List headerBytes = Uint8List(5);
headerBytes[0] = header.type.value; // 类型
headerBytes[1] = (header.seqNum >> 8) & 0xFF; // 序列号高字节
headerBytes[2] = header.seqNum & 0xFF; // 序列号低字节
headerBytes[3] = (header.dataLen >> 8) & 0xFF; // 数据长度高字节
headerBytes[4] = header.dataLen & 0xFF; // 数据长度低字节
// 合并包头和数据
Uint8List fullData = Uint8List.fromList(headerBytes + packetData);
// 计算CRC
int crc = crc16(fullData);
return Packet(header, packetData, crc);
}

View File

@ -0,0 +1,282 @@
import 'dart:async';
import 'dart:typed_data';
import 'package:flutter/foundation.dart';
import 'packet.dart';
// 检查数据长度是否合理 (可以设置一个最大值以防止异常)
const int maxDataLength = 1024 + 7;
/// 处理接收蓝牙数据的解析器,将字节流解析为数据包结构
class Parser {
/// 接收数据缓冲区
final List<int> _buffer = [];
final Function(Packet) onParser; // 回调函数
Parser(this.onParser);
/// 添加数据到缓冲区并尝试解析
void add(List<int> data) {
// 添加数据到缓冲区
_buffer.addAll(data);
// 尝试解析数据包
_processBuffer();
}
/// 处理缓冲区,尝试解析出完整的数据包
void _processBuffer() {
// 包头需要至少5个字节
while (_buffer.length >= 7) {
// 检查包类型是否有效
int typeValue = _buffer[0];
if (typeValue < 0x01 || typeValue > 0x06) {
// 无效包类型,丢弃当前字节并继续
_buffer.removeAt(0);
continue;
}
// 获取包类型
PacketType type = PacketType.fromValue(typeValue);
// 解析序列号和数据长度
int seqNum = (_buffer[1] << 8) | _buffer[2];
int dataLen = (_buffer[3] << 8) | _buffer[4];
// 检查数据长度是否合理 (可以设置一个最大值以防止异常)
if (dataLen < 0 || dataLen > maxDataLength) {
// 假设最大数据长度为1KB
// 数据长度异常,丢弃当前字节并继续
_buffer.removeAt(0);
continue;
}
// 检查缓冲区是否有足够的数据 (包头5字节 + 数据长度 + CRC 2字节)
int totalPacketLength = 5 + dataLen + 2;
if (_buffer.length < totalPacketLength) {
// 数据不完整,等待更多数据
break;
}
// 提取完整的数据包
List<int> packetBytes = _buffer.sublist(0, totalPacketLength);
// 验证CRC
int expectedCrc = (packetBytes[totalPacketLength - 2] << 8) | packetBytes[totalPacketLength - 1];
List<int> dataToCheck = packetBytes.sublist(0, totalPacketLength - 2);
int calculatedCrc = calculateCrc16(Uint8List.fromList(dataToCheck));
if (calculatedCrc != expectedCrc) {
// CRC校验失败丢弃当前字节并继续
_buffer.removeAt(0);
continue;
}
// CRC校验通过创建数据包对象
PacketHeader header = PacketHeader(type, seqNum, dataLen);
Uint8List data = Uint8List.fromList(_buffer.sublist(5, 5 + dataLen));
Packet packet = Packet(header, data, expectedCrc);
// 发送解析出的数据包
onParser(packet); // 回调函数
// 从缓冲区移除已处理的数据
_buffer.removeRange(0, totalPacketLength);
}
}
/// 计算CRC16校验值
int calculateCrc16(Uint8List data) {
int crc = 0;
for (int byte in data) {
crc = (crc ^ byte) & 0xFFFF;
for (int i = 0; i < 8; i++) {
if ((crc & 0x0001) != 0) {
crc = ((crc >> 1) ^ 0xA001) & 0xFFFF;
} else {
crc = (crc >> 1) & 0xFFFF;
}
}
}
return crc;
}
/// 创建开始包
Packet createStartPacket(int fileSize, int blockSize, String fileName) {
// 创建数据: [文件大小(4字节) + 块大小(2字节) + 文件名(n字节)]
// 文件大小: 高位在前
List<int> data = [
(fileSize >> 24) & 0xFF,
(fileSize >> 16) & 0xFF,
(fileSize >> 8) & 0xFF,
fileSize & 0xFF,
(blockSize >> 8) & 0xFF,
blockSize & 0xFF,
];
// 添加文件名的ASCII码
data.addAll(fileName.codeUnits);
// 创建包头
PacketHeader header = PacketHeader(PacketType.start, 0, data.length);
// 计算CRC
Uint8List headerBytes = Uint8List(5);
headerBytes[0] = header.type.value;
headerBytes[1] = (header.seqNum >> 8) & 0xFF;
headerBytes[2] = header.seqNum & 0xFF;
headerBytes[3] = (header.dataLen >> 8) & 0xFF;
headerBytes[4] = header.dataLen & 0xFF;
Uint8List dataToCheck = Uint8List.fromList([...headerBytes, ...data]);
int crc = calculateCrc16(dataToCheck);
return Packet(header, Uint8List.fromList(data), crc);
}
/// 创建数据包
Packet createDataPacket(int seqNum, Uint8List data) {
// 创建包头
PacketHeader header = PacketHeader(PacketType.data, seqNum, data.length);
// 计算CRC
Uint8List headerBytes = Uint8List(5);
headerBytes[0] = header.type.value;
headerBytes[1] = (header.seqNum >> 8) & 0xFF;
headerBytes[2] = header.seqNum & 0xFF;
headerBytes[3] = (header.dataLen >> 8) & 0xFF;
headerBytes[4] = header.dataLen & 0xFF;
Uint8List dataToCheck = Uint8List.fromList([...headerBytes, ...data]);
int crc = calculateCrc16(dataToCheck);
return Packet(header, data, crc);
}
/// 创建确认包
Packet createAckPacket(int seqNum) {
// 确认包的数据部分包含被确认的包序号
Uint8List data = Uint8List(2);
data[0] = (seqNum >> 8) & 0xFF;
data[1] = seqNum & 0xFF;
// 创建包头
PacketHeader header = PacketHeader(PacketType.ack, seqNum, data.length);
// 计算CRC
Uint8List headerBytes = Uint8List(5);
headerBytes[0] = header.type.value;
headerBytes[1] = (header.seqNum >> 8) & 0xFF;
headerBytes[2] = header.seqNum & 0xFF;
headerBytes[3] = (header.dataLen >> 8) & 0xFF;
headerBytes[4] = header.dataLen & 0xFF;
Uint8List dataToCheck = Uint8List.fromList([...headerBytes, ...data]);
int crc = calculateCrc16(dataToCheck);
return Packet(header, data, crc);
}
/// 创建重传请求包
Packet createNakPacket(int seqNum) {
// 重传请求包的数据部分包含需要重传的包序号
Uint8List data = Uint8List(2);
data[0] = (seqNum >> 8) & 0xFF;
data[1] = seqNum & 0xFF;
// 创建包头
PacketHeader header = PacketHeader(PacketType.nak, seqNum, data.length);
// 计算CRC
Uint8List headerBytes = Uint8List(5);
headerBytes[0] = header.type.value;
headerBytes[1] = (header.seqNum >> 8) & 0xFF;
headerBytes[2] = header.seqNum & 0xFF;
headerBytes[3] = (header.dataLen >> 8) & 0xFF;
headerBytes[4] = header.dataLen & 0xFF;
Uint8List dataToCheck = Uint8List.fromList([...headerBytes, ...data]);
int crc = calculateCrc16(dataToCheck);
return Packet(header, data, crc);
}
/// 创建结束包
Packet createEndPacket(int seqNum) {
// 结束包的数据部分为空
Uint8List data = Uint8List(0);
// 创建包头
PacketHeader header = PacketHeader(PacketType.end, seqNum, data.length);
// 计算CRC
Uint8List headerBytes = Uint8List(5);
headerBytes[0] = header.type.value;
headerBytes[1] = (header.seqNum >> 8) & 0xFF;
headerBytes[2] = header.seqNum & 0xFF;
headerBytes[3] = (header.dataLen >> 8) & 0xFF;
headerBytes[4] = header.dataLen & 0xFF;
Uint8List dataToCheck = Uint8List.fromList([...headerBytes]);
int crc = calculateCrc16(dataToCheck);
return Packet(header, data, crc);
}
/// 创建错误包
Packet createErrorPacket(int errorCode) {
// 错误包的数据部分包含错误码
Uint8List data = Uint8List(1);
data[0] = errorCode & 0xFF;
// 创建包头 (错误包序号默认为0)
PacketHeader header = PacketHeader(PacketType.error, 0, data.length);
// 计算CRC
Uint8List headerBytes = Uint8List(5);
headerBytes[0] = header.type.value;
headerBytes[1] = (header.seqNum >> 8) & 0xFF;
headerBytes[2] = header.seqNum & 0xFF;
headerBytes[3] = (header.dataLen >> 8) & 0xFF;
headerBytes[4] = header.dataLen & 0xFF;
Uint8List dataToCheck = Uint8List.fromList([...headerBytes, ...data]);
int crc = calculateCrc16(dataToCheck);
return Packet(header, data, crc);
}
/// 解析开始包中的数据
Map<String, dynamic> parseStartPacketData(Uint8List data) {
// 文件大小: 4字节
int fileSize = (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3];
// 块大小: 2字节
int blockSize = (data[4] << 8) | data[5];
// 文件名: 剩余字节
String fileName = String.fromCharCodes(data.sublist(6));
return {
'fileSize': fileSize,
'blockSize': blockSize,
'fileName': fileName,
};
}
/// 解析确认包和重传请求包中的序列号
int parseAckNakPacketData(Uint8List data) {
// 序列号: 2字节
return (data[0] << 8) | data[1];
}
/// 解析错误包中的错误码
int parseErrorPacketData(Uint8List data) {
// 错误码: 1字节
return data[0];
}
/// 重置解析器
void reset() => _buffer.clear();
}

View File

@ -4,14 +4,34 @@ import 'dart:typed_data';
import 'package:bluetooth_low_energy/bluetooth_low_energy.dart';
import 'package:bluetooth_low_energy_example/models.dart';
import 'package:bluetooth_low_energy_example/utils/logger.dart';
import 'package:clover/clover.dart';
import 'package:logging/logging.dart';
import 'packet.dart';
import 'parser.dart';
import 'receiver.dart';
class PeripheralManagerViewModel extends ViewModel {
final PeripheralManager _manager;
final List<Log> _logs;
bool _advertising;
late Parser parser = Parser(handleParser);
late Receiver receiver = Receiver(manager: _manager);
void handleParser(Packet packet) {
logger.i(
'收到 ${packet.type}',
'序号: ${packet.header.seqNum}',
'总长度: ${packet.toBytes().length},数据长度: ${packet.data.length}',
);
// 将接收到的数据包传递给 Receiver 处理
receiver.handlePacket(packet);
}
late final StreamSubscription _stateChangedSubscription;
late final StreamSubscription _characteristicReadRequestedSubscription;
late final StreamSubscription _characteristicWriteRequestedSubscription;
@ -22,14 +42,12 @@ class PeripheralManagerViewModel extends ViewModel {
_logs = [],
_advertising = false {
_stateChangedSubscription = _manager.stateChanged.listen((eventArgs) async {
if (eventArgs.state == BluetoothLowEnergyState.unauthorized &&
Platform.isAndroid) {
if (eventArgs.state == BluetoothLowEnergyState.unauthorized && Platform.isAndroid) {
await _manager.authorize();
}
notifyListeners();
});
_characteristicReadRequestedSubscription =
_manager.characteristicReadRequested.listen((eventArgs) async {
_characteristicReadRequestedSubscription = _manager.characteristicReadRequested.listen((eventArgs) async {
final central = eventArgs.central;
final characteristic = eventArgs.characteristic;
final request = eventArgs.request;
@ -48,24 +66,36 @@ class PeripheralManagerViewModel extends ViewModel {
value: trimmedValue,
);
});
_characteristicWriteRequestedSubscription =
_manager.characteristicWriteRequested.listen((eventArgs) async {
/// 订阅写入数据请求
int receivedTotal = 0;
_characteristicWriteRequestedSubscription = _manager.characteristicWriteRequested.listen((eventArgs) async {
// 收到APP写入数据请求
final central = eventArgs.central;
final characteristic = eventArgs.characteristic;
receiver.central = eventArgs.central;
receiver.characteristic = eventArgs.characteristic;
final request = eventArgs.request;
final offset = request.offset;
final value = request.value;
final log = Log(
type: 'Characteristic write requested',
message:
'[${value.length}] ${central.uuid}, ${characteristic.uuid}, $offset, $value',
message: '[${value.length}] ${central.uuid}, ${characteristic.uuid}, $offset, ${value.map((e) => e.toRadixString(16)).join(",")}',
);
_logs.add(log);
notifyListeners();
await _manager.respondWriteRequest(request);
// 解析数据包
// logger.i(
// '解析数据包,长度${eventArgs.request.value.length}',
// '[${eventArgs.request.value.map((e) => e.toRadixString(16)).join(",")}]',
// );
receivedTotal += value.length;
logger.i("接受总量:$receivedTotal");
parser.add(value);
});
_characteristicNotifyStateChangedSubscription =
_manager.characteristicNotifyStateChanged.listen((eventArgs) async {
_characteristicNotifyStateChangedSubscription = _manager.characteristicNotifyStateChanged.listen((eventArgs) async {
print('Characteristic notify state changed');
final central = eventArgs.central;
final characteristic = eventArgs.characteristic;
final state = eventArgs.state;
@ -77,8 +107,7 @@ class PeripheralManagerViewModel extends ViewModel {
notifyListeners();
// Write someting to the central when notify started.
if (state) {
final maximumNotifyLength =
await _manager.getMaximumNotifyLength(central);
final maximumNotifyLength = await _manager.getMaximumNotifyLength(central);
final elements = List.generate(maximumNotifyLength, (i) => i % 256);
final value = Uint8List.fromList(elements);
await _manager.notifyCharacteristic(
@ -91,7 +120,9 @@ class PeripheralManagerViewModel extends ViewModel {
}
BluetoothLowEnergyState get state => _manager.state;
bool get advertising => _advertising;
List<Log> get logs => _logs;
Future<void> showAppSettings() async {

View File

@ -0,0 +1,58 @@
// 接收方类
import 'dart:typed_data';
import 'package:bluetooth_low_energy/bluetooth_low_energy.dart';
import 'package:bluetooth_low_energy_example/utils/common.dart';
import 'package:bluetooth_low_energy_example/utils/logger.dart';
import 'packet.dart';
class Receiver {
List<Uint8List> receivedChunks = [];
int expectedSeqNum = 1;
Central? central;
GATTCharacteristic? characteristic;
PeripheralManager? manager;
Receiver({this.central, this.characteristic, this.manager});
void handlePacket(Packet packet) {
if (packet.header.type == PacketType.start) {
int fileSize = (packet.data[0] << 24) | (packet.data[1] << 16) | (packet.data[2] << 8) | packet.data[3];
int chunkSize = (packet.data[4] << 8) | packet.data[5];
logger.i('收到Start包: 文件大小=$fileSize, 包大小=$chunkSize');
expectedSeqNum = 1;
receivedChunks = [];
sendAck(0);
} else if (packet.header.type == PacketType.data) {
if (packet.header.seqNum == expectedSeqNum) {
receivedChunks.add(packet.data);
// logger.i('收到Data包: 序号=${packet.header.seqNum}');
sendAck(packet.header.seqNum);
expectedSeqNum++;
} else {
logger.i('收到乱序Data包: 序号=${packet.header.seqNum},期望=$expectedSeqNum');
sendNak(expectedSeqNum);
}
} else if (packet.header.type == PacketType.end) {
logger.i('收到End包传输完成');
sendAck(packet.header.seqNum);
}
}
void sendAck(int seqNum) {
Packet ackPacket = packPacket(PacketType.ack, seqNum, Uint8List.fromList([seqNum >> 8, seqNum & 0xFF]));
logger.i('接收方发送ACK: ${ackPacket.toBytes().map((e) => toHexString(e)).join(',')}');
if (manager != null && central != null && characteristic != null) {
manager!.notifyCharacteristic(central!, characteristic!, value: ackPacket.toBytes());
}
}
void sendNak(int seqNum) {
Packet nakPacket = packPacket(PacketType.nak, seqNum, Uint8List.fromList([seqNum >> 8, seqNum & 0xFF]));
logger.i('接收方发送NAK: $seqNum');
if (manager != null && central != null && characteristic != null) {
manager!.notifyCharacteristic(central!, characteristic!, value: nakPacket.toBytes());
}
}
}

View File

@ -23,7 +23,7 @@ packages:
path: ".."
relative: true
source: path
version: "6.0.1"
version: "6.0.2"
bluetooth_low_energy_android:
dependency: transitive
description:
@ -108,10 +108,10 @@ packages:
dependency: "direct main"
description:
name: collection
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf
url: "https://pub.dev"
source: hosted
version: "1.18.0"
version: "1.19.0"
convert:
dependency: "direct main"
description:
@ -234,18 +234,18 @@ packages:
dependency: transitive
description:
name: leak_tracker
sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a"
sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06"
url: "https://pub.dev"
source: hosted
version: "10.0.4"
version: "10.0.7"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8"
sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379"
url: "https://pub.dev"
source: hosted
version: "3.0.3"
version: "3.0.8"
leak_tracker_testing:
dependency: transitive
description:
@ -262,6 +262,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.0.0"
logger:
dependency: "direct main"
description:
name: logger
sha256: be4b23575aac7ebf01f225a241eb7f6b5641eeaf43c6a8613510fc2f8cf187d1
url: "https://pub.dev"
source: hosted
version: "2.5.0"
logging:
dependency: "direct main"
description:
@ -282,10 +290,10 @@ packages:
dependency: transitive
description:
name: material_color_utilities
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
url: "https://pub.dev"
source: hosted
version: "0.8.0"
version: "0.11.1"
material_symbols_icons:
dependency: "direct main"
description:
@ -298,10 +306,10 @@ packages:
dependency: transitive
description:
name: meta
sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136"
sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
url: "https://pub.dev"
source: hosted
version: "1.12.0"
version: "1.15.0"
path:
dependency: transitive
description:
@ -322,10 +330,10 @@ packages:
dependency: transitive
description:
name: platform
sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec"
sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65"
url: "https://pub.dev"
source: hosted
version: "3.1.4"
version: "3.1.5"
plugin_platform_interface:
dependency: transitive
description:
@ -346,7 +354,7 @@ packages:
dependency: transitive
description: flutter
source: sdk
version: "0.0.99"
version: "0.0.0"
source_span:
dependency: transitive
description:
@ -359,10 +367,10 @@ packages:
dependency: transitive
description:
name: stack_trace
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377"
url: "https://pub.dev"
source: hosted
version: "1.11.1"
version: "1.12.0"
stream_channel:
dependency: transitive
description:
@ -375,10 +383,10 @@ packages:
dependency: transitive
description:
name: string_scanner
sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3"
url: "https://pub.dev"
source: hosted
version: "1.2.0"
version: "1.3.0"
sync_http:
dependency: transitive
description:
@ -399,10 +407,10 @@ packages:
dependency: transitive
description:
name: test_api
sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f"
sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c"
url: "https://pub.dev"
source: hosted
version: "0.7.0"
version: "0.7.3"
typed_data:
dependency: transitive
description:
@ -423,18 +431,18 @@ packages:
dependency: transitive
description:
name: vm_service
sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec"
sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b
url: "https://pub.dev"
source: hosted
version: "14.2.1"
version: "14.3.0"
webdriver:
dependency: transitive
description:
name: webdriver
sha256: "003d7da9519e1e5f329422b36c4dcdf18d7d2978d1ba099ea4e45ba490ed845e"
sha256: "3d773670966f02a646319410766d3b5e1037efb7f07cc68f844d5e06cd4d61c8"
url: "https://pub.dev"
source: hosted
version: "3.0.3"
version: "3.0.4"
xml:
dependency: transitive
description:

View File

@ -37,6 +37,7 @@ dependencies:
convert: ^3.1.1
flutter_simple_treeview: ^3.0.2
material_symbols_icons: ^4.2744.0
logger: #日志
dev_dependencies:
integration_test: