From 74476afc45434f736d2221d7987b722aaeaf99d0 Mon Sep 17 00:00:00 2001 From: HVBT Dev Date: Mon, 27 Jul 2026 00:25:39 +0530 Subject: [PATCH] Improve live S300 update responsiveness --- lib/core/bluetooth/bt_poller.dart | 12 +++++++--- lib/core/providers/settings_provider.dart | 5 ++--- lib/ui/screens/settings/settings_screen.dart | 23 ++++++++------------ 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/lib/core/bluetooth/bt_poller.dart b/lib/core/bluetooth/bt_poller.dart index aeb42b2..c546799 100644 --- a/lib/core/bluetooth/bt_poller.dart +++ b/lib/core/bluetooth/bt_poller.dart @@ -8,7 +8,7 @@ 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]. +/// and emits the newest usable frame on [frameStream]. class BtPoller { final BtService _service; EcuType ecuType; @@ -34,7 +34,7 @@ class BtPoller { BtPoller( this._service, { this.ecuType = EcuType.s300, - this.pollingInterval = const Duration(milliseconds: 100), + this.pollingInterval = const Duration(milliseconds: 50), }); void start() { @@ -66,6 +66,8 @@ class BtPoller { // 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. + Uint8List? latestUsableFrame; + while (_rxBuf.length >= 128) { // Scan for 0x1B frame header int startIdx = 0; @@ -91,7 +93,7 @@ class BtPoller { if (canUseFrame) { if (!checksumOk) droppedFrames++; validFrames++; - _frameController.add(frame); + latestUsableFrame = frame; _rxBuf.removeRange(0, startIdx + 128); } else { // Bad checksum — skip this byte and try again @@ -99,6 +101,10 @@ class BtPoller { _rxBuf.removeRange(0, startIdx + 1); } } + + if (latestUsableFrame != null) { + _frameController.add(latestUsableFrame); + } } void stop() { diff --git a/lib/core/providers/settings_provider.dart b/lib/core/providers/settings_provider.dart index a58e236..5adad0d 100644 --- a/lib/core/providers/settings_provider.dart +++ b/lib/core/providers/settings_provider.dart @@ -7,7 +7,7 @@ class AppSettings { const AppSettings({ this.ecuType = EcuType.s300, - this.pollingInterval = const Duration(milliseconds: 100), + this.pollingInterval = const Duration(milliseconds: 50), }); AppSettings copyWith({EcuType? ecuType, Duration? pollingInterval}) => @@ -25,7 +25,6 @@ class SettingsNotifier extends StateNotifier { state = state.copyWith(pollingInterval: d); } -final settingsProvider = - StateNotifierProvider( +final settingsProvider = StateNotifierProvider( (ref) => SettingsNotifier(), ); diff --git a/lib/ui/screens/settings/settings_screen.dart b/lib/ui/screens/settings/settings_screen.dart index f5d5617..d56837f 100644 --- a/lib/ui/screens/settings/settings_screen.dart +++ b/lib/ui/screens/settings/settings_screen.dart @@ -27,11 +27,9 @@ class SettingsScreen extends ConsumerWidget { leading: Icon(Icons.bluetooth, color: colors.accent), title: const Text('Bluetooth Device'), subtitle: Text( - bt.isConnected - ? 'Connected: ${bt.deviceName}' - : 'Not connected', - style: TextStyle( - color: bt.isConnected ? Colors.green : Colors.grey), + bt.isConnected ? 'Connected: ${bt.deviceName}' : 'Not connected', + style: + TextStyle(color: bt.isConnected ? Colors.green : Colors.grey), ), trailing: bt.isConnected ? TextButton( @@ -47,8 +45,7 @@ class SettingsScreen extends ConsumerWidget { ? null : () => Navigator.push( context, - MaterialPageRoute( - builder: (_) => const BtPickerScreen()), + MaterialPageRoute(builder: (_) => const BtPickerScreen()), ), ), const Divider(height: 1, color: Colors.white10), @@ -63,8 +60,7 @@ class SettingsScreen extends ConsumerWidget { groupValue: settings.ecuType, activeColor: colors.accent, onChanged: (v) { - if (v != null) - ref.read(settingsProvider.notifier).setEcuType(v); + if (v != null) ref.read(settingsProvider.notifier).setEcuType(v); }, ), RadioListTile( @@ -75,15 +71,14 @@ class SettingsScreen extends ConsumerWidget { groupValue: settings.ecuType, activeColor: colors.accent, onChanged: (v) { - if (v != null) - ref.read(settingsProvider.notifier).setEcuType(v); + if (v != null) ref.read(settingsProvider.notifier).setEcuType(v); }, ), const Divider(height: 1, color: Colors.white10), // ── Polling Interval ───────────────────────────────────── _SectionHeader('POLLING INTERVAL'), - ...[50, 100, 200].map((ms) => RadioListTile( + ...[20, 50, 100, 200].map((ms) => RadioListTile( title: Text('${ms}ms (~${(1000 / ms).round()} Hz)'), value: ms, groupValue: settings.pollingInterval.inMilliseconds, @@ -102,8 +97,8 @@ class SettingsScreen extends ConsumerWidget { _SectionHeader('THEME'), ...AppThemeVariant.values.map((v) { final label = switch (v) { - AppThemeVariant.redDark => 'Red — Dark', - AppThemeVariant.redLight => 'Red — Light', + AppThemeVariant.redDark => 'Red — Dark', + AppThemeVariant.redLight => 'Red — Light', AppThemeVariant.greenDark => 'Green — Dark', AppThemeVariant.greenLight => 'Green — Light', };