Files
MobilApp/lib/pages/vibro_nav/presentation/controllers/vibro_nav_controller.dart
T

229 lines
8.5 KiB
Dart
Raw Normal View History

2026-07-13 07:58:51 +02:00
import 'dart:async';
import 'dart:math' as math;
2026-07-15 03:13:52 +02:00
import 'package:flutter_map/flutter_map.dart';
2026-07-13 07:58:51 +02:00
import 'package:get/get.dart';
2026-07-15 03:13:52 +02:00
import 'package:latlong2/latlong.dart';
2026-07-13 07:58:51 +02:00
import 'package:terepi_seged/models/source_point.dart';
import 'package:terepi_seged/models/vechicle_position_log.dart';
import 'package:terepi_seged/services/app_database.dart';
import 'package:terepi_seged/services/coord_converter_service.dart';
import 'package:terepi_seged/services/device_identity_service.dart';
import 'package:terepi_seged/services/gnss/gnss_service.dart';
import 'package:terepi_seged/services/project_service.dart';
import 'package:terepi_seged/services/vechicle_identity_service.dart';
/// Egyszerű navigációs panel a jelgerjesztő (vibrátor) járműhöz.
///
/// A pozíció a KITŰZÉSSEL AZONOS forrásból jön (GnssService — külső
/// BT/BLE GNSS-egység a járműben), semmilyen új eszköz-integráció nem
/// kell hozzá. A forráspontok tisztán referencia-réteg (nincs mutálható
/// "meglőve" állapotuk); az ellenőrzés a periodikus pozíciónapló és a
/// terv UTÓLAGOS összevetéséből adódik (lásd isVerified).
class VibroNavController extends GetxController {
AppDatabase get _db => AppDatabase.instance;
// ── Forráspontok ─────────────────────────────────────────────────
final sourcePoints = <SourcePoint>[].obs;
/// Egyezés-tűrés (m) a naplózott pozíció és a terv-forráspont között.
final tolerance = 15.0.obs;
// ── Élő pozíció (a GnssService-ből, EOV-ra konvertálva) ───────────
final hasPosition = false.obs;
final curEovY = 0.0.obs;
final curEovX = 0.0.obs;
final speedKmh = 0.0.obs;
final sessionDistanceM = 0.0.obs;
/// Haladási irány (fok) — pozíció-előzményből számolva, ALACSONY
/// SEBESSÉGNÉL BEFAGYASZTVA (a nyers GPS-heading álló/lassú helyzetben
/// zajos/értelmetlen — ugyanez a minta, mint a Kitűzésnél).
final travelHeading = Rxn<double>();
static const _headingFreezeSpeedKmh = 5.0;
double? _histY, _histX;
DateTime? _histTime;
// ── Legközelebbi forráspont ───────────────────────────────────────
final nearestPoint = Rxn<SourcePoint>();
final nearestDistance = 0.0.obs;
// ── Periodikus napló ───────────────────────────────────────────────
final isLogging = false.obs;
final logIntervalSec = 60.obs; // felhasználó által állítható
2026-07-15 03:13:52 +02:00
final logs = <VehiclePositionLog>[].obs;
2026-07-13 07:58:51 +02:00
Timer? _logTimer;
2026-07-15 03:13:52 +02:00
// ── Térkép — a MapController itt él, nem a View State-jében ────────
final mapController = MapController();
2026-07-13 07:58:51 +02:00
@override
void onInit() {
super.onInit();
load();
2026-07-15 03:13:52 +02:00
refreshLogs();
2026-07-13 07:58:51 +02:00
if (Get.isRegistered<GnssService>()) {
ever(GnssService.to.lastGgaLine, (_) => _onPosition());
}
}
@override
void onClose() {
_logTimer?.cancel();
super.onClose();
}
2026-07-15 03:13:52 +02:00
Future<void> refreshLogs() async {
logs.value = await loadLogs();
}
2026-07-13 07:58:51 +02:00
Future<void> load() async {
final projectId = ProjectService.to.activeProjectId;
if (projectId == null) {
sourcePoints.clear();
return;
}
sourcePoints.value = await _db.listSourcePoints(projectId);
}
// ═════════════════════════════════════════════════════════════════
// Pozíció-feldolgozás
// ═════════════════════════════════════════════════════════════════
void _onPosition() {
final gnss = GnssService.to;
if (gnss.gpsQuality.value <= 0 ||
gnss.latitude.value == 0 ||
!Get.isRegistered<CoordConverterService>()) {
return;
}
final p = CoordConverterService.to
.wgsToEovPoint(gnss.longitude.value, gnss.latitude.value);
final now = DateTime.now();
if (_histY != null && _histTime != null) {
final dy = p.x - _histY!;
final dx = p.y - _histX!;
final dist = math.sqrt(dy * dy + dx * dx);
final dtSec = now.difference(_histTime!).inMilliseconds / 1000.0;
if (dtSec > 0) {
final v = dist / dtSec; // m/s
speedKmh.value = v * 3.6;
sessionDistanceM.value += dist;
// Irány csak akkor frissül, ha a sebesség a fagyasztási küszöb
// felett van — álló/nagyon lassú helyzetben a nyers irány zajos.
if (speedKmh.value >= _headingFreezeSpeedKmh && dist > 0.3) {
travelHeading.value =
(math.atan2(dy, dx) * 180 / math.pi + 360) % 360;
}
}
}
_histY = p.x;
_histX = p.y;
_histTime = now;
curEovY.value = p.x;
curEovX.value = p.y;
hasPosition.value = true;
_updateNearest();
}
void _updateNearest() {
if (sourcePoints.isEmpty) {
nearestPoint.value = null;
return;
}
SourcePoint? best;
var bestDist = double.infinity;
for (final sp in sourcePoints) {
final dy = sp.planEovY - curEovY.value;
final dx = sp.planEovX - curEovX.value;
final d = math.sqrt(dy * dy + dx * dx);
if (d < bestDist) {
bestDist = d;
best = sp;
}
}
nearestPoint.value = best;
nearestDistance.value = bestDist;
}
// ═════════════════════════════════════════════════════════════════
// Terv/napló összevetés — melyik forráspont "igazolt"
// ═════════════════════════════════════════════════════════════════
/// Egyszerű, szinkron ellenőrzés a már betöltött [logs] lista alapján
/// (a nézet előre lekéri, hogy ne fusson adatbázis-lekérdezés minden
/// egyes marker kirajzolásakor).
bool isVerified(SourcePoint sp, List<VehiclePositionLog> logs) {
for (final log in logs) {
final dy = sp.planEovY - log.eovY;
final dx = sp.planEovX - log.eovX;
if (math.sqrt(dy * dy + dx * dx) <= tolerance.value) return true;
}
return false;
}
Future<List<VehiclePositionLog>> loadLogs() async {
final projectId = ProjectService.to.activeProjectId;
if (projectId == null) return [];
return _db.listVehiclePositionLogs(projectId);
}
// ═════════════════════════════════════════════════════════════════
// Periodikus rögzítés
// ═════════════════════════════════════════════════════════════════
void startLogging() {
if (isLogging.value) return;
isLogging.value = true;
_logTick(); // azonnali első pont
_logTimer = Timer.periodic(
Duration(seconds: logIntervalSec.value), (_) => _logTick());
}
void stopLogging() {
isLogging.value = false;
_logTimer?.cancel();
_logTimer = null;
}
Future<void> _logTick() async {
if (!hasPosition.value) return;
final projectId = ProjectService.to.activeProjectId;
final vehicleId = VehicleIdentityService.to.selectedVehicle.value;
if (projectId == null || vehicleId == null) return;
final gnss = GnssService.to;
final entry = VehiclePositionLog(
projectId: projectId,
vehicleId: vehicleId,
eovY: curEovY.value,
eovX: curEovX.value,
lat: gnss.latitude.value,
lon: gnss.longitude.value,
altitude: gnss.altitude.value,
speedKmh: speedKmh.value,
heading: travelHeading.value,
fixQuality: gnss.gpsQuality.value,
accuracy: gnss.horizontalAccuracy,
deviceId: Get.isRegistered<DeviceIdentityService>() &&
DeviceIdentityService.to.isReady
? DeviceIdentityService.to.deviceId
: null,
appInstanceId: Get.isRegistered<DeviceIdentityService>() &&
DeviceIdentityService.to.isReady
? DeviceIdentityService.to.appInstanceId
: null,
);
await _db.insertVehiclePositionLog(entry);
2026-07-15 03:13:52 +02:00
logs.add(entry);
2026-07-13 07:58:51 +02:00
}
}