137 lines
4.7 KiB
Dart
137 lines
4.7 KiB
Dart
import 'dart:math' as math;
|
|
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
import 'package:bully/services/parallelism_service.dart';
|
|
|
|
/// Vecteur de gravité mesuré par l'accéléromètre pour une pose donnée.
|
|
///
|
|
/// [pitch] et [roll] sont exprimés comme dans le service : le tangage vaut 0°
|
|
/// appareil vertical et +90° appareil à plat caméra vers le sol.
|
|
({double x, double y, double z}) gravity({
|
|
double pitch = 0,
|
|
double roll = 0,
|
|
}) {
|
|
const double g = 9.81;
|
|
final double p = pitch * math.pi / 180.0;
|
|
final double r = roll * math.pi / 180.0;
|
|
|
|
final double nz = math.sin(p);
|
|
final double nx = math.sin(r);
|
|
// Ce qui reste va sur y, l'axe vertical de l'écran.
|
|
final double ny = math.sqrt(math.max(0.0, 1.0 - nz * nz - nx * nx));
|
|
|
|
return (x: nx * g, y: ny * g, z: nz * g);
|
|
}
|
|
|
|
ParallelismData evaluatePose(
|
|
ParallelismService service, {
|
|
double pitch = 0,
|
|
double roll = 0,
|
|
}) {
|
|
final v = gravity(pitch: pitch, roll: roll);
|
|
final data = service.evaluate(v.x, v.y, v.z);
|
|
expect(data, isNotNull, reason: 'mesure jugée aberrante à tort');
|
|
return data!;
|
|
}
|
|
|
|
void main() {
|
|
group('pose cible au mur (comportement existant)', () {
|
|
test('appareil vertical → aligné', () {
|
|
final data = evaluatePose(ParallelismService());
|
|
|
|
expect(data.pose, TargetPose.wall);
|
|
expect(data.status, ParallelismStatus.aligned);
|
|
expect(data.pitchDeviation, closeTo(0, 0.5));
|
|
});
|
|
|
|
test('l\'écart de tangage reste le tangage brut', () {
|
|
final data = evaluatePose(ParallelismService(), pitch: 10);
|
|
|
|
expect(data.pose, TargetPose.wall);
|
|
expect(data.pitchDeviation, closeTo(data.pitchDegrees, 0.001));
|
|
});
|
|
|
|
test('inclinaison au-delà du seuil → désaligné', () {
|
|
final data = evaluatePose(ParallelismService(), pitch: 40);
|
|
|
|
expect(data.pose, TargetPose.wall);
|
|
expect(data.status, ParallelismStatus.misaligned);
|
|
});
|
|
|
|
test('roulis excessif → désaligné même si le tangage est bon', () {
|
|
final data = evaluatePose(ParallelismService(), roll: 35);
|
|
|
|
expect(data.status, ParallelismStatus.misaligned);
|
|
});
|
|
});
|
|
|
|
group('pose cible au sol', () {
|
|
test('appareil à plat caméra vers le bas → aligné', () {
|
|
final data = evaluatePose(ParallelismService(), pitch: 90);
|
|
|
|
expect(data.pose, TargetPose.ground);
|
|
expect(data.status, ParallelismStatus.aligned);
|
|
expect(data.pitchDeviation, closeTo(0, 0.5));
|
|
});
|
|
|
|
test('l\'écart se mesure par rapport à +90°, pas à 0°', () {
|
|
final data = evaluatePose(ParallelismService(), pitch: 80);
|
|
|
|
expect(data.pose, TargetPose.ground);
|
|
expect(data.pitchDeviation, closeTo(-10, 0.5));
|
|
expect(data.status, ParallelismStatus.aligned);
|
|
});
|
|
|
|
test('à plat mais trop incliné → désaligné', () {
|
|
final data = evaluatePose(ParallelismService(), pitch: 50);
|
|
|
|
expect(data.status, ParallelismStatus.misaligned);
|
|
});
|
|
|
|
test('à plat écran vers le haut → jamais aligné', () {
|
|
// La caméra vise le plafond : le vert n'aurait aucun sens.
|
|
final data = evaluatePose(ParallelismService(), pitch: -90);
|
|
|
|
expect(data.pose, TargetPose.wall);
|
|
expect(data.status, ParallelismStatus.misaligned);
|
|
});
|
|
});
|
|
|
|
group('hystérésis', () {
|
|
test('le vert survit à un léger tremblement entre les deux seuils', () {
|
|
final service = ParallelismService();
|
|
|
|
expect(evaluatePose(service).status, ParallelismStatus.aligned);
|
|
// 28° est au-dessus du seuil d'entrée (25°) mais sous celui de sortie (32°).
|
|
expect(evaluatePose(service, pitch: 28).status, ParallelismStatus.aligned);
|
|
expect(evaluatePose(service, pitch: 40).status, ParallelismStatus.misaligned);
|
|
// Repasser sous 32° ne suffit pas : il faut redescendre sous 25°.
|
|
expect(evaluatePose(service, pitch: 28).status, ParallelismStatus.misaligned);
|
|
expect(evaluatePose(service, pitch: 20).status, ParallelismStatus.aligned);
|
|
});
|
|
|
|
test('la pose ne bascule pas pour un écart négligeable', () {
|
|
final service = ParallelismService();
|
|
|
|
expect(evaluatePose(service).pose, TargetPose.wall);
|
|
// Pile à la frontière des deux poses : on garde celle déjà retenue.
|
|
expect(evaluatePose(service, pitch: 45).pose, TargetPose.wall);
|
|
// Franchement du côté du sol : on bascule.
|
|
expect(evaluatePose(service, pitch: 70).pose, TargetPose.ground);
|
|
});
|
|
|
|
test('stop() remet la pose et le statut à zéro', () {
|
|
final service = ParallelismService();
|
|
|
|
expect(evaluatePose(service, pitch: 90).pose, TargetPose.ground);
|
|
service.stop();
|
|
expect(evaluatePose(service).pose, TargetPose.wall);
|
|
});
|
|
});
|
|
|
|
test('une mesure aberrante est ignorée', () {
|
|
expect(ParallelismService().evaluate(0, 0, 0), isNull);
|
|
});
|
|
}
|