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 _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 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(); }