hondavert-dev/lib/core/providers/bt_provider.dart
HVBT Dev 6a80d8fc1f v1.2.0 — Phase 2: Riverpod providers wired up
- 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
2026-04-13 20:13:32 +05:30

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();
});