MotorSignal-App/lib/features/rides/ride_state_machine.dart

174 lines
4.6 KiB
Dart

import 'dart:async';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../database/database.dart';
import 'package:drift/drift.dart' as drift;
import 'package:geolocator/geolocator.dart';
enum RideState { idle, ready, riding, paused, finished }
class RideSession {
final int? dbId;
final RideState state;
final double tripDistanceMiles;
final double topSpeedMph;
final double maxLeanLeft;
final double maxLeanRight;
final double maxAccelG;
final double maxBrakingG;
final DateTime? startTime;
RideSession({
this.dbId,
this.state = RideState.idle,
this.tripDistanceMiles = 0.0,
this.topSpeedMph = 0.0,
this.maxLeanLeft = 0.0,
this.maxLeanRight = 0.0,
this.maxAccelG = 0.0,
this.maxBrakingG = 0.0,
this.startTime,
});
RideSession copyWith({
int? dbId,
RideState? state,
double? tripDistanceMiles,
double? topSpeedMph,
double? maxLeanLeft,
double? maxLeanRight,
double? maxAccelG,
double? maxBrakingG,
DateTime? startTime,
}) {
return RideSession(
dbId: dbId ?? this.dbId,
state: state ?? this.state,
tripDistanceMiles: tripDistanceMiles ?? this.tripDistanceMiles,
topSpeedMph: topSpeedMph ?? this.topSpeedMph,
maxLeanLeft: maxLeanLeft ?? this.maxLeanLeft,
maxLeanRight: maxLeanRight ?? this.maxLeanRight,
maxAccelG: maxAccelG ?? this.maxAccelG,
maxBrakingG: maxBrakingG ?? this.maxBrakingG,
startTime: startTime ?? this.startTime,
);
}
}
class RideStateMachine extends StateNotifier<RideSession> {
final AppDatabase db;
Position? _lastPosition;
RideStateMachine(this.db) : super(RideSession());
void setReady() {
state = state.copyWith(state: RideState.ready);
}
Future<void> startRide() async {
final id = await db.startRide();
state = state.copyWith(
dbId: id,
state: RideState.riding,
startTime: DateTime.now(),
tripDistanceMiles: 0,
topSpeedMph: 0,
);
_lastPosition = null;
}
void pauseRide() {
state = state.copyWith(state: RideState.paused);
}
void resumeRide() {
state = state.copyWith(state: RideState.riding);
_lastPosition = null; // reset so we don't jump distance
}
Future<void> finishRide() async {
if (state.dbId != null) {
await db.endRide(
state.dbId!,
RidesCompanion(
distanceMiles: drift.Value(state.tripDistanceMiles),
topSpeedMph: drift.Value(state.topSpeedMph),
maxLeanLeft: drift.Value(state.maxLeanLeft),
maxLeanRight: drift.Value(state.maxLeanRight),
maxAccelG: drift.Value(state.maxAccelG),
maxBrakingG: drift.Value(state.maxBrakingG),
),
);
}
state = state.copyWith(state: RideState.finished);
}
void reset() {
state = RideSession();
}
void updateTelemetry({
Position? position,
double? speedMph,
double? leanAngle,
double? accelG,
double? lateralG,
}) {
if (state.state != RideState.riding) return;
double newTopSpeed = state.topSpeedMph;
double newMaxLeanLeft = state.maxLeanLeft;
double newMaxLeanRight = state.maxLeanRight;
double newMaxAccelG = state.maxAccelG;
double newMaxBrakingG = state.maxBrakingG;
double newDistance = state.tripDistanceMiles;
if (speedMph != null && speedMph > state.topSpeedMph) {
newTopSpeed = speedMph;
}
if (leanAngle != null) {
if (leanAngle < 0 && leanAngle.abs() > state.maxLeanLeft) {
newMaxLeanLeft = leanAngle.abs();
} else if (leanAngle > 0 && leanAngle > state.maxLeanRight) {
newMaxLeanRight = leanAngle;
}
}
if (accelG != null) {
if (accelG > 0 && accelG > state.maxAccelG) {
newMaxAccelG = accelG;
} else if (accelG < 0 && accelG.abs() > state.maxBrakingG) {
newMaxBrakingG = accelG.abs();
}
}
if (position != null) {
if (_lastPosition != null) {
// Calculate distance in meters
final distMeters = Geolocator.distanceBetween(
_lastPosition!.latitude,
_lastPosition!.longitude,
position.latitude,
position.longitude,
);
newDistance += distMeters / 1609.34; // convert to miles
}
_lastPosition = position;
}
state = state.copyWith(
topSpeedMph: newTopSpeed,
maxLeanLeft: newMaxLeanLeft,
maxLeanRight: newMaxLeanRight,
maxAccelG: newMaxAccelG,
maxBrakingG: newMaxBrakingG,
tripDistanceMiles: newDistance,
);
}
}
final rideStateMachineProvider = StateNotifierProvider<RideStateMachine, RideSession>((ref) {
final db = ref.watch(databaseProvider);
return RideStateMachine(db);
});