obd_economy.dart 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. /// Rolling 5 km fuel economy calculated from MAF, speed and throttle.
  2. ///
  3. /// Petrol constants: stoichiometric AFR 14.7 and 745 g/L. This is deliberately
  4. /// an estimate; the dashboard presents it as a trend, not a measurement.
  5. class ObdEconomy {
  6. static const windowKm = 5.0;
  7. static const minimumKm = 0.5;
  8. static const _maxGap = Duration(seconds: 30);
  9. static const _afr = 14.7;
  10. static const _fuelDensityGramsPerLitre = 745.0;
  11. final List<_Segment> _segments = [];
  12. DateTime? _lastAt;
  13. double? _lastSpeedKmh;
  14. double? _lastMafGps;
  15. double? get litresPer100Km {
  16. if (_segments.isEmpty) return null;
  17. var km = 0.0;
  18. var litres = 0.0;
  19. for (final segment in _segments) {
  20. km += segment.km;
  21. litres += segment.litres;
  22. }
  23. if (km < minimumKm || km <= 0) return null;
  24. return (100 * litres / km).clamp(0.0, 99.9);
  25. }
  26. double get accumulatedKm {
  27. return _segments.fold(0, (total, segment) => total + segment.km);
  28. }
  29. void reset() {
  30. _segments.clear();
  31. _lastAt = null;
  32. _lastSpeedKmh = null;
  33. _lastMafGps = null;
  34. }
  35. void add({
  36. required DateTime at,
  37. required double? speedKmh,
  38. required double? mafGps,
  39. required double? throttlePercent,
  40. }) {
  41. final previousAt = _lastAt;
  42. _lastAt = at;
  43. if (speedKmh == null || mafGps == null) {
  44. _lastSpeedKmh = null;
  45. _lastMafGps = null;
  46. return;
  47. }
  48. if (previousAt == null ||
  49. _lastSpeedKmh == null ||
  50. _lastMafGps == null ||
  51. at.difference(previousAt) > _maxGap ||
  52. at.isBefore(previousAt)) {
  53. if (previousAt != null && at.difference(previousAt) > _maxGap) {
  54. reset();
  55. }
  56. _lastAt = at;
  57. _lastSpeedKmh = speedKmh;
  58. _lastMafGps = mafGps;
  59. return;
  60. }
  61. final deltaSeconds = at.difference(previousAt).inMilliseconds / 1000.0;
  62. if (deltaSeconds <= 0) return;
  63. final fuelCut =
  64. throttlePercent != null && throttlePercent < 1 && speedKmh > 10;
  65. final litresPerHour = fuelCut
  66. ? 0.0
  67. : (mafGps / _afr) * 3600 / _fuelDensityGramsPerLitre;
  68. final km = speedKmh * deltaSeconds / 3600;
  69. if (km > 0) {
  70. _segments.add(
  71. _Segment(km: km, litres: litresPerHour * deltaSeconds / 3600),
  72. );
  73. _trim();
  74. }
  75. _lastAt = at;
  76. _lastSpeedKmh = speedKmh;
  77. _lastMafGps = mafGps;
  78. }
  79. void _trim() {
  80. var km = accumulatedKm;
  81. while (km > windowKm && _segments.isNotEmpty) {
  82. km -= _segments.removeAt(0).km;
  83. }
  84. }
  85. }
  86. class _Segment {
  87. const _Segment({required this.km, required this.litres});
  88. final double km;
  89. final double litres;
  90. }