- settings_provider: AppSettings (ecuType, pollingInterval) StateNotifier - bt_provider: BtNotifier (disconnected/connecting/connected/error states), btServiceProvider singleton, pairedDevicesProvider FutureProvider, internal frameStream piped through StreamController - sensor_provider: sensorStateProvider StreamProvider (auto S300/KPro), latestSensorProvider convenience Provider - theme_provider: AppThemeVariant (red/green × dark/light) StateNotifier - main.dart: fully Riverpod — ProviderScope, ConsumerWidget throughout, no setState for BT state, providers own all lifecycle
139 lines
4.4 KiB
Dart
139 lines
4.4 KiB
Dart
import 'dart:async';
|
|
import 'dart:typed_data';
|
|
|
|
import 'package:flutter_bluetooth_serial/flutter_bluetooth_serial.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
|
|
import '../bluetooth/bt_poller.dart';
|
|
import '../bluetooth/bt_service.dart';
|
|
import 'settings_provider.dart';
|
|
|
|
// ─── Connection status enum ──────────────────────────────────────────────────
|
|
|
|
enum BtStatus { disconnected, connecting, connected, error }
|
|
|
|
// ─── State ───────────────────────────────────────────────────────────────────
|
|
|
|
class BtState {
|
|
final BtStatus status;
|
|
final String? deviceName;
|
|
final String? error;
|
|
final int frameCount;
|
|
final int droppedFrames;
|
|
|
|
const BtState({
|
|
required this.status,
|
|
this.deviceName,
|
|
this.error,
|
|
this.frameCount = 0,
|
|
this.droppedFrames = 0,
|
|
});
|
|
|
|
const BtState.disconnected()
|
|
: this(status: BtStatus.disconnected);
|
|
|
|
BtState connecting(String name) =>
|
|
BtState(status: BtStatus.connecting, deviceName: name);
|
|
|
|
BtState connected(String name) =>
|
|
BtState(status: BtStatus.connected, deviceName: name);
|
|
|
|
BtState withError(String msg) =>
|
|
BtState(status: BtStatus.error, deviceName: deviceName, error: msg);
|
|
|
|
BtState withFrameStats(int frames, int dropped) => BtState(
|
|
status: status,
|
|
deviceName: deviceName,
|
|
frameCount: frames,
|
|
droppedFrames: dropped,
|
|
);
|
|
|
|
bool get isConnected => status == BtStatus.connected;
|
|
bool get isConnecting => status == BtStatus.connecting;
|
|
}
|
|
|
|
// ─── Notifier ────────────────────────────────────────────────────────────────
|
|
|
|
class BtNotifier extends StateNotifier<BtState> {
|
|
final Ref _ref;
|
|
BtPoller? _poller;
|
|
StreamSubscription<Uint8List>? _pollSub;
|
|
final StreamController<Uint8List> _frameController =
|
|
StreamController<Uint8List>.broadcast();
|
|
|
|
BtNotifier(this._ref) : super(const BtState.disconnected());
|
|
|
|
/// Raw validated 128-byte frame stream — consumed by sensorStateProvider.
|
|
Stream<Uint8List> get frameStream => _frameController.stream;
|
|
|
|
Future<void> connect(BluetoothDevice device) async {
|
|
state = state.connecting(device.name ?? device.address);
|
|
final service = _ref.read(btServiceProvider);
|
|
final settings = _ref.read(settingsProvider);
|
|
|
|
try {
|
|
await service.connect(device);
|
|
|
|
_poller = BtPoller(
|
|
service,
|
|
ecuType: settings.ecuType,
|
|
pollingInterval: settings.pollingInterval,
|
|
);
|
|
|
|
_pollSub = _poller!.frameStream.listen(
|
|
(frame) {
|
|
_frameController.add(frame);
|
|
state = state.withFrameStats(
|
|
_poller!.validFrames, _poller!.droppedFrames);
|
|
},
|
|
onError: (Object e) {
|
|
_frameController.addError(e);
|
|
state = state.withError(e.toString());
|
|
},
|
|
onDone: () => state = state.withError('BT stream closed'),
|
|
);
|
|
|
|
_poller!.start();
|
|
state = state.connected(device.name ?? device.address);
|
|
} catch (e) {
|
|
await service.disconnect();
|
|
state = state.withError(e.toString());
|
|
}
|
|
}
|
|
|
|
Future<void> disconnect() async {
|
|
await _pollSub?.cancel();
|
|
_poller?.stop();
|
|
_poller?.dispose();
|
|
_poller = null;
|
|
await _ref.read(btServiceProvider).disconnect();
|
|
state = const BtState.disconnected();
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_pollSub?.cancel();
|
|
_poller?.dispose();
|
|
_frameController.close();
|
|
super.dispose();
|
|
}
|
|
}
|
|
|
|
// ─── Providers ───────────────────────────────────────────────────────────────
|
|
|
|
/// Singleton BtService — lives as long as the app.
|
|
final btServiceProvider = Provider<BtService>((ref) {
|
|
final service = BtService();
|
|
ref.onDispose(service.dispose);
|
|
return service;
|
|
});
|
|
|
|
/// BT connection state notifier.
|
|
final btProvider =
|
|
StateNotifierProvider<BtNotifier, BtState>((ref) => BtNotifier(ref));
|
|
|
|
/// Paired BT devices list (re-fetchable via ref.refresh).
|
|
final pairedDevicesProvider = FutureProvider<List<BluetoothDevice>>((ref) {
|
|
return FlutterBluetoothSerial.instance.getBondedDevices();
|
|
});
|