- 移除自定义时间戳+随机数ID生成逻辑 - 集成uuid包依赖并配置版本 - 使用Uuid.v4()替换原有next()方法实现 - 更新MessageIdGenerator类文档注释 - 在JSON协议层添加设备日志警告输出 - 修改pubspec.yaml添加uuid依赖声明
97 lines
3.2 KiB
Dart
97 lines
3.2 KiB
Dart
import 'dart:convert';
|
||
import 'dart:typed_data';
|
||
|
||
import 'device_log.dart';
|
||
import 'device_message.dart';
|
||
|
||
/// JSON 协议层帧编解码器
|
||
///
|
||
/// 由于串口为字节流,需要在 JSON 字符串之外增加帧定界。
|
||
/// 采用「4 字节大端长度前缀 + UTF-8 JSON + 换行符」的简单可靠方案:
|
||
/// ```
|
||
/// [LEN(4B BE)] [JSON_UTF8...] ['\n']
|
||
/// ```
|
||
/// - LEN:JSON 部分的字节数(不含 LEN 自身与换行符)
|
||
/// - JSON:完整 `DeviceMessage.toJson()` 序列化结果
|
||
/// - 换行符:辅助日志/调试,便于人工嗅探串口
|
||
///
|
||
/// 解析器按累积缓冲区增量解码,单次返回一条已完整解析的消息。
|
||
class JsonProtocolService {
|
||
static const int _lengthPrefixSize = 4;
|
||
static const int _maxFrameBytes = 64 * 1024;
|
||
|
||
final MessageIdGenerator _idGenerator = MessageIdGenerator();
|
||
final List<int> _buffer = [];
|
||
|
||
/// 生成下一个消息 ID
|
||
String nextId() => _idGenerator.next();
|
||
|
||
/// 将消息编码为可下发的字节流
|
||
Uint8List encode(DeviceMessage message) {
|
||
final json = utf8.encode(message.encode());
|
||
if (json.isEmpty) {
|
||
throw ArgumentError('encoded JSON is empty');
|
||
}
|
||
if (json.length > _maxFrameBytes) {
|
||
throw ArgumentError('message too large: ${json.length}B');
|
||
}
|
||
final buf = Uint8List(_lengthPrefixSize + json.length + 1);
|
||
// 大端长度
|
||
final len = json.length;
|
||
buf[0] = (len >> 24) & 0xFF;
|
||
buf[1] = (len >> 16) & 0xFF;
|
||
buf[2] = (len >> 8) & 0xFF;
|
||
buf[3] = len & 0xFF;
|
||
buf.setRange(_lengthPrefixSize, _lengthPrefixSize + json.length, json);
|
||
buf[buf.length - 1] = 0x0A; // '\n'
|
||
return buf;
|
||
}
|
||
|
||
/// 尝试从累积缓冲区解析一条完整消息
|
||
///
|
||
/// 返回 (message, consumedBytes)。consumedBytes 表示已消费字节数,
|
||
/// 调用方应从缓冲区中移除。
|
||
/// 不足一帧时返回 (null, 0)。
|
||
(DeviceMessage?, int) tryDecode(List<int> incoming) {
|
||
if (incoming.isEmpty) return (null, 0);
|
||
_buffer.addAll(incoming);
|
||
|
||
// 防止缓冲区无限增长
|
||
if (_buffer.length > _maxFrameBytes * 2) {
|
||
_buffer.removeRange(0, _buffer.length - _maxFrameBytes);
|
||
}
|
||
|
||
if (_buffer.length < _lengthPrefixSize) return (null, 0);
|
||
|
||
final len = (_buffer[0] << 24) |
|
||
(_buffer[1] << 16) |
|
||
(_buffer[2] << 8) |
|
||
_buffer[3];
|
||
if (len <= 0 || len > _maxFrameBytes) {
|
||
DeviceLog.warn('tryDecode: 异常长度=$len 丢弃首字节 0x${_buffer[0].toRadixString(16).padLeft(2, '0')}');
|
||
_buffer.removeAt(0);
|
||
return (null, 0);
|
||
}
|
||
|
||
final totalNeeded = _lengthPrefixSize + len + 1; // +1 换行符
|
||
if (_buffer.length < totalNeeded) return (null, 0);
|
||
|
||
final jsonBytes = _buffer.sublist(_lengthPrefixSize, _lengthPrefixSize + len);
|
||
final tail = _buffer[_lengthPrefixSize + len];
|
||
// 换行符不是必需的,缺失也接受;存在则跳过
|
||
final consumed = tail == 0x0A ? totalNeeded : totalNeeded - 1;
|
||
_buffer.removeRange(0, consumed);
|
||
|
||
try {
|
||
final json = utf8.decode(jsonBytes);
|
||
final msg = DeviceMessage.decode(json);
|
||
return (msg, consumed);
|
||
} catch (_) {
|
||
return (null, consumed);
|
||
}
|
||
}
|
||
|
||
/// 重置内部缓冲区(断线/异常时调用)
|
||
void reset() => _buffer.clear();
|
||
}
|