Improve live S300 update responsiveness

This commit is contained in:
HVBT Dev 2026-07-27 00:25:39 +05:30
parent 0886bef200
commit 74476afc45
3 changed files with 20 additions and 20 deletions

View File

@ -8,7 +8,7 @@ enum EcuType { s300, kpro }
/// Sends the ECU request byte every [pollingInterval] ms, /// Sends the ECU request byte every [pollingInterval] ms,
/// accumulates raw bytes into 128-byte frames, validates NEG8, /// 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 { class BtPoller {
final BtService _service; final BtService _service;
EcuType ecuType; EcuType ecuType;
@ -34,7 +34,7 @@ class BtPoller {
BtPoller( BtPoller(
this._service, { this._service, {
this.ecuType = EcuType.s300, this.ecuType = EcuType.s300,
this.pollingInterval = const Duration(milliseconds: 100), this.pollingInterval = const Duration(milliseconds: 50),
}); });
void start() { void start() {
@ -66,6 +66,8 @@ class BtPoller {
// Extract as many 128-byte frames as possible. // Extract as many 128-byte frames as possible.
// Frame start sync: first byte should be 0x1B (header marker). // Frame start sync: first byte should be 0x1B (header marker).
// If we have a misaligned buffer, scan forward. // If we have a misaligned buffer, scan forward.
Uint8List? latestUsableFrame;
while (_rxBuf.length >= 128) { while (_rxBuf.length >= 128) {
// Scan for 0x1B frame header // Scan for 0x1B frame header
int startIdx = 0; int startIdx = 0;
@ -91,7 +93,7 @@ class BtPoller {
if (canUseFrame) { if (canUseFrame) {
if (!checksumOk) droppedFrames++; if (!checksumOk) droppedFrames++;
validFrames++; validFrames++;
_frameController.add(frame); latestUsableFrame = frame;
_rxBuf.removeRange(0, startIdx + 128); _rxBuf.removeRange(0, startIdx + 128);
} else { } else {
// Bad checksum skip this byte and try again // Bad checksum skip this byte and try again
@ -99,6 +101,10 @@ class BtPoller {
_rxBuf.removeRange(0, startIdx + 1); _rxBuf.removeRange(0, startIdx + 1);
} }
} }
if (latestUsableFrame != null) {
_frameController.add(latestUsableFrame);
}
} }
void stop() { void stop() {

View File

@ -7,7 +7,7 @@ class AppSettings {
const AppSettings({ const AppSettings({
this.ecuType = EcuType.s300, this.ecuType = EcuType.s300,
this.pollingInterval = const Duration(milliseconds: 100), this.pollingInterval = const Duration(milliseconds: 50),
}); });
AppSettings copyWith({EcuType? ecuType, Duration? pollingInterval}) => AppSettings copyWith({EcuType? ecuType, Duration? pollingInterval}) =>
@ -25,7 +25,6 @@ class SettingsNotifier extends StateNotifier<AppSettings> {
state = state.copyWith(pollingInterval: d); state = state.copyWith(pollingInterval: d);
} }
final settingsProvider = final settingsProvider = StateNotifierProvider<SettingsNotifier, AppSettings>(
StateNotifierProvider<SettingsNotifier, AppSettings>(
(ref) => SettingsNotifier(), (ref) => SettingsNotifier(),
); );

View File

@ -27,11 +27,9 @@ class SettingsScreen extends ConsumerWidget {
leading: Icon(Icons.bluetooth, color: colors.accent), leading: Icon(Icons.bluetooth, color: colors.accent),
title: const Text('Bluetooth Device'), title: const Text('Bluetooth Device'),
subtitle: Text( subtitle: Text(
bt.isConnected bt.isConnected ? 'Connected: ${bt.deviceName}' : 'Not connected',
? 'Connected: ${bt.deviceName}' style:
: 'Not connected', TextStyle(color: bt.isConnected ? Colors.green : Colors.grey),
style: TextStyle(
color: bt.isConnected ? Colors.green : Colors.grey),
), ),
trailing: bt.isConnected trailing: bt.isConnected
? TextButton( ? TextButton(
@ -47,8 +45,7 @@ class SettingsScreen extends ConsumerWidget {
? null ? null
: () => Navigator.push( : () => Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(builder: (_) => const BtPickerScreen()),
builder: (_) => const BtPickerScreen()),
), ),
), ),
const Divider(height: 1, color: Colors.white10), const Divider(height: 1, color: Colors.white10),
@ -63,8 +60,7 @@ class SettingsScreen extends ConsumerWidget {
groupValue: settings.ecuType, groupValue: settings.ecuType,
activeColor: colors.accent, activeColor: colors.accent,
onChanged: (v) { onChanged: (v) {
if (v != null) if (v != null) ref.read(settingsProvider.notifier).setEcuType(v);
ref.read(settingsProvider.notifier).setEcuType(v);
}, },
), ),
RadioListTile<EcuType>( RadioListTile<EcuType>(
@ -75,15 +71,14 @@ class SettingsScreen extends ConsumerWidget {
groupValue: settings.ecuType, groupValue: settings.ecuType,
activeColor: colors.accent, activeColor: colors.accent,
onChanged: (v) { onChanged: (v) {
if (v != null) if (v != null) ref.read(settingsProvider.notifier).setEcuType(v);
ref.read(settingsProvider.notifier).setEcuType(v);
}, },
), ),
const Divider(height: 1, color: Colors.white10), const Divider(height: 1, color: Colors.white10),
// Polling Interval // Polling Interval
_SectionHeader('POLLING INTERVAL'), _SectionHeader('POLLING INTERVAL'),
...[50, 100, 200].map((ms) => RadioListTile<int>( ...[20, 50, 100, 200].map((ms) => RadioListTile<int>(
title: Text('${ms}ms (~${(1000 / ms).round()} Hz)'), title: Text('${ms}ms (~${(1000 / ms).round()} Hz)'),
value: ms, value: ms,
groupValue: settings.pollingInterval.inMilliseconds, groupValue: settings.pollingInterval.inMilliseconds,
@ -102,8 +97,8 @@ class SettingsScreen extends ConsumerWidget {
_SectionHeader('THEME'), _SectionHeader('THEME'),
...AppThemeVariant.values.map((v) { ...AppThemeVariant.values.map((v) {
final label = switch (v) { final label = switch (v) {
AppThemeVariant.redDark => 'Red — Dark', AppThemeVariant.redDark => 'Red — Dark',
AppThemeVariant.redLight => 'Red — Light', AppThemeVariant.redLight => 'Red — Light',
AppThemeVariant.greenDark => 'Green — Dark', AppThemeVariant.greenDark => 'Green — Dark',
AppThemeVariant.greenLight => 'Green — Light', AppThemeVariant.greenLight => 'Green — Light',
}; };