Improve live S300 update responsiveness
This commit is contained in:
parent
0886bef200
commit
74476afc45
@ -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() {
|
||||||
|
|||||||
@ -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(),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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',
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user