import 'dart:async'; import 'dart:typed_data'; import '../protocol/neg8.dart'; import 'bt_service.dart'; enum EcuType { s300, kpro } /// Sends the ECU request byte every [pollingInterval] ms, /// accumulates raw bytes into 128-byte frames, validates NEG8, /// and emits valid frames on [frameStream]. class BtPoller { final BtService _service; EcuType ecuType; final Duration pollingInterval; // S300 request: [0x1B, 0x00, 0xE5] static final Uint8List _s300Request = Uint8List.fromList([0x1B, 0x00, 0xE5]); // KPro request: [0x1B, 0x01, 0xE4] static final Uint8List _kproRequest = Uint8List.fromList([0x1B, 0x01, 0xE4]); final StreamController _frameController = StreamController.broadcast(); Stream get frameStream => _frameController.stream; final List _rxBuf = []; Timer? _pollTimer; StreamSubscription? _rxSub; int droppedFrames = 0; int validFrames = 0; BtPoller( this._service, { this.ecuType = EcuType.s300, this.pollingInterval = const Duration(milliseconds: 100), }); void start() { _rxBuf.clear(); // Listen to raw bytes from BT and accumulate into 128-byte frames _rxSub = _service.rawStream.listen( (Uint8List chunk) { _rxBuf.addAll(chunk); _processBuffer(); }, onError: (Object e) { _frameController.addError(e); stop(); }, ); // Send request at polling interval _pollTimer = Timer.periodic(pollingInterval, (_) async { if (_service.isConnected) { await _service.write( ecuType == EcuType.s300 ? _s300Request : _kproRequest, ); } }); } void _processBuffer() { // Extract as many 128-byte frames as possible. // Frame start sync: first byte should be 0x1B (header marker). // If we have a misaligned buffer, scan forward. while (_rxBuf.length >= 128) { // Scan for 0x1B frame header int startIdx = 0; while (startIdx < _rxBuf.length && _rxBuf[startIdx] != 0x1B) { startIdx++; } // Not enough data after sync byte if (_rxBuf.length - startIdx < 128) { // Discard bytes before potential header if (startIdx > 0) { _rxBuf.removeRange(0, startIdx); } break; } final Uint8List frame = Uint8List.fromList(_rxBuf.sublist(startIdx, startIdx + 128)); if (validateFrame(frame)) { validFrames++; _frameController.add(frame); _rxBuf.removeRange(0, startIdx + 128); } else { // Bad checksum — skip this byte and try again droppedFrames++; _rxBuf.removeRange(0, startIdx + 1); } } } void stop() { _pollTimer?.cancel(); _pollTimer = null; _rxSub?.cancel(); _rxSub = null; _rxBuf.clear(); } void dispose() { stop(); _frameController.close(); } }