From fdbf6c872ad909c3d75a23cb03824ef6667fa878 Mon Sep 17 00:00:00 2001 From: Dynesshely Date: Sat, 30 Mar 2024 15:08:07 +0800 Subject: [PATCH 1/6] =?UTF-8?q?=F0=9F=92=BE=20Feat(Rotation):=20Use=20quat?= =?UTF-8?q?ernion=20instead=20of=20euler=20angles.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gyroscope_display_stand.dart | 69 +++++----- .../utils/emulators/rotation_emulator.dart | 120 +++++++----------- 2 files changed, 84 insertions(+), 105 deletions(-) diff --git a/kitx_mobile/lib/pages/test_pages/sensors_display_stands/gyroscope_display_stand.dart b/kitx_mobile/lib/pages/test_pages/sensors_display_stands/gyroscope_display_stand.dart index 0a2a08a..146e1ea 100644 --- a/kitx_mobile/lib/pages/test_pages/sensors_display_stands/gyroscope_display_stand.dart +++ b/kitx_mobile/lib/pages/test_pages/sensors_display_stands/gyroscope_display_stand.dart @@ -20,11 +20,8 @@ class GyroscopeDisplayStandState extends State { /// Gyroscope direction x, y, z final directionX = 'none'.obs, directionY = 'none'.obs, directionZ = 'none'.obs; - /// Drawing canvas width - static double canvasWidth = 400; - - /// Drawing canvas height - static double canvasHeight = 300; + /// Drawing canvas size + static double canvasWidth = 400, canvasHeight = 300; /// Is drawing paused var rotationPaused = false.obs; @@ -39,17 +36,25 @@ class GyroscopeDisplayStandState extends State { void initState() { painter.initialize(); - gyroscopeDataListener = gyroscopeEventStream(samplingPeriod: Duration(milliseconds: 50)).listen((event) { - DeviceRotationHost.rotateWithAcceleration(event.x, event.y, event.z, 0.05); - - dirX.value = event.x; - dirY.value = event.y; - dirZ.value = event.z; - - directionX.value = dirX >= 0 ? '⇊' : '⇈'; - directionY.value = dirY >= 0 ? '↻' : '↺'; - directionZ.value = dirZ >= 0 ? '↶' : '↷'; - }); + gyroscopeDataListener = gyroscopeEventStream(samplingPeriod: Duration(milliseconds: 50)).listen( + (event) { + DeviceRotationHost.rotateWithAcceleration(event.x, event.y, event.z, 0.05); + + dirX.value = event.x; + dirY.value = event.y; + dirZ.value = event.z; + + directionX.value = dirX >= 0 ? '⇊' : '⇈'; + directionY.value = dirY >= 0 ? '↻' : '↺'; + directionZ.value = dirZ >= 0 ? '↶' : '↷'; + }, + onError: (error) { + Timer.periodic(Duration(milliseconds: 50), (timer) { + DeviceRotationHost.rotateWithAcceleration(0, 0, 0.5, 0.05); + }); + }, + cancelOnError: true, + ); super.initState(); } @@ -142,22 +147,19 @@ class Painter extends CustomPainter { /// Return absolute value double abs(double num) => num >= 0 ? num : -num; - /// Yaw - Pitch - Roll - vector_math.Vector3 getRotationAngles() => DeviceRotationHost.getRotationAngles(); - /// Initialize void initialize() { - DeviceRotationHost.axis = vector_math.Vector3(objectWidth / 2, 0, 0); - DeviceRotationHost.ayis = vector_math.Vector3(0, objectHeight / 2, 0); - DeviceRotationHost.azis = vector_math.Vector3(0, 0, 1); + DeviceRotationHost.xDir = vector_math.Quaternion(objectWidth / 2, 0, 0, 0); + DeviceRotationHost.yDir = vector_math.Quaternion(0, objectHeight / 2, 0, 0); + DeviceRotationHost.zDir = vector_math.Quaternion(0, 0, 1, 0); DeviceRotationHost.setPoints([ - vector_math.Vector3(-objectWidth / 2, objectHeight / 2, 0), - vector_math.Vector3(objectWidth / 2, objectHeight / 2, 0), - vector_math.Vector3(objectWidth / 2, -objectHeight / 2, 0), - vector_math.Vector3(-objectWidth / 2, -objectHeight / 2, 0), - vector_math.Vector3(-objectWidth / 2 + objectWidth / 4, -objectHeight / 2 + objectHeight / 24, 0), - vector_math.Vector3(objectWidth / 2 - objectWidth / 4, -objectHeight / 2 + objectHeight / 24, 0), + vector_math.Quaternion(-objectWidth / 2, objectHeight / 2, 0, 0), + vector_math.Quaternion(objectWidth / 2, objectHeight / 2, 0, 0), + vector_math.Quaternion(objectWidth / 2, -objectHeight / 2, 0, 0), + vector_math.Quaternion(-objectWidth / 2, -objectHeight / 2, 0, 0), + vector_math.Quaternion(-objectWidth / 2 + objectWidth / 4, -objectHeight / 2 + objectHeight / 24, 0, 0), + vector_math.Quaternion(objectWidth / 2 - objectWidth / 4, -objectHeight / 2 + objectHeight / 24, 0, 0), ]); } @@ -168,7 +170,7 @@ class Painter extends CustomPainter { List displayPoints = []; for (int i = 0; i < rotatedPoints.length; ++i) { - displayPoints.add(getCrossPoint(rotatedPoints[i], camera, null, null) ?? vector_math.Vector3(0, 0, 0)); + displayPoints.add(getCrossPoint(rotatedPoints[i].toPoint(), camera, null, null) ?? vector_math.Vector3(0, 0, 0)); } return displayPoints; @@ -185,11 +187,14 @@ class Painter extends CustomPainter { @override void paint(Canvas canvas, Size size) { - var angles = getRotationAngles(); + // var angles = getRotationAngles(); var points = getPoints(); - var isBack = abs(angles.y) > 90 || abs(angles.z) > 90; + // var isBack = abs(angles.y) > 90 || abs(angles.z) > 90; + // var paint = Paint() + // ..color = isBack ? Colors.blue : Colors.red + // ..strokeWidth = 1.0; var paint = Paint() - ..color = isBack ? Colors.blue : Colors.red + ..color = Colors.blue ..strokeWidth = 1.0; var a = points[0]; diff --git a/kitx_mobile/lib/utils/emulators/rotation_emulator.dart b/kitx_mobile/lib/utils/emulators/rotation_emulator.dart index 909dfaf..278ea9c 100644 --- a/kitx_mobile/lib/utils/emulators/rotation_emulator.dart +++ b/kitx_mobile/lib/utils/emulators/rotation_emulator.dart @@ -5,6 +5,9 @@ import 'package:vector_math/vector_math.dart'; /// PI const double pi = 3.1415926535897938324626433832795028841971; +/// Per rad equals 57.29 degrees +const double perRadToDegrees = 57.29577951308232; + /// Get direction Vector3 getDirection(Vector3 p1, Vector3 p2) => Vector3(p2.x - p1.x, p2.y - p1.y, p2.z - p1.z); @@ -36,92 +39,44 @@ Vector3? getCrossPoint(Vector3 p, Vector3 camera, Vector3? n, Vector3? plane) { } } -/// Rotate -Vector3 rotate(Vector3 p, Vector3 angles) => rotateAll(p, angles.x, angles.y, angles.z); - -/// Rotate all -Vector3 rotateAll(Vector3 p, double yaw, double pitch, double roll) => rotateX( - rotateY( - rotateZ(p, yaw), - pitch, - ), - roll, - ); - -/// Rotate X -Vector3 rotateX(Vector3 p, double alpha) { - alpha *= pi / 180; - return Vector3( - cos(alpha) * p.x + sin(alpha) * p.z, - p.y, - -sin(alpha) * p.x + cos(alpha) * p.z, - ); -} - -/// Rotate Y -Vector3 rotateY(Vector3 p, double beta) { - beta *= pi / 180; - return Vector3( - p.x, - cos(beta) * p.y - sin(beta) * p.z, - sin(beta) * p.y + cos(beta) * p.z, - ); -} - -/// Rotate Z -Vector3 rotateZ(Vector3 p, double gamma) { - gamma *= pi / 180; - return Vector3( - cos(gamma) * p.x - sin(gamma) * p.y, - sin(gamma) * p.x + cos(gamma) * p.y, - p.z, - ); -} - /// DeviceRotationHost class DeviceRotationHost { - /// Per rad equals 57.29 degrees - static double perRadToDegrees = 57.29577951308232; - - /// Rotation degrees - static double yaw = 0, pitch = 0, roll = 0; + /// Directions + static Quaternion xDir = Quaternion(1, 0, 0, 0), yDir = Quaternion(0, 1, 0, 0), zDir = Quaternion(0, 0, 1, 0); - /// Get rotation angles - static Vector3 getRotationAngles() => Vector3(yaw, pitch, roll); - - /// Default axis, ayis, azis - static Vector3 axis = Vector3(1, 0, 0), ayis = Vector3(0, 1, 0), azis = Vector3(0, 0, 1); + /// The last time's directions rotation + static Quaternion lastXDirR = Quaternion(1, 0, 0, 0), lastYDirR = Quaternion(0, 1, 0, 0), lastZDirR = Quaternion(0, 0, 1, 0); /// Points to rotate - static List points = [], originPoints = []; + static List points = [], originPoints = []; /// 显式调用此方法来计算加速度之后的四元数 /// [gyroX], [gyroY], [gyroZ] 单位: rad/s, [samplingRate] 单位: s - /// yaw -> z - /// pitch -> y - /// roll -> x static void rotateWithAcceleration(double gyroX, double gyroY, double gyroZ, double samplingRate) { - yaw += gyroZ * samplingRate * perRadToDegrees; - pitch += gyroX * samplingRate * perRadToDegrees; - roll += gyroY * samplingRate * perRadToDegrees; - - var qx = Quaternion.axisAngle(axis, -gyroX * samplingRate); - var qy = Quaternion.axisAngle(ayis, -gyroY * samplingRate); - var qz = Quaternion.axisAngle(azis, -gyroZ * samplingRate); + var radX = gyroX * samplingRate, radY = gyroY * samplingRate, radZ = gyroZ * samplingRate; - var q = qx * qy * qz; + var xDirR = xDir.ewNormalize().wpRotate(radX); + var yDirR = yDir.ewNormalize().wpRotate(radY); + var zDirR = zDir.ewNormalize().wpRotate(radZ); - q.normalize(); + var xDirRInv = xDirR.clone().conjugated(); + var yDirRInv = yDirR.clone().conjugated(); + var zDirRInv = zDirR.clone().conjugated(); for (int i = 0; i < points.length; ++i) { - qz.rotate(points[i]); - qy.rotate(points[i]); - qx.rotate(points[i]); + var np = points[i].clone(); + np = xDirR * np * xDirRInv; + np = yDirR * np * yDirRInv; + np = zDirR * np * zDirRInv; + points[i] = np; } } /// Set points - static void setPoints(List newPoints) { + static void setPoints(List newPoints) { + originPoints.clear(); + points.clear(); + for (int i = 0; i < newPoints.length; ++i) { points.add(newPoints[i].clone()); originPoints.add(newPoints[i].clone()); @@ -130,12 +85,31 @@ class DeviceRotationHost { /// Restore quaternion static void restore() { - yaw = 0; - pitch = 0; - roll = 0; - for (int i = 0; i < points.length; ++i) { points[i] = originPoints[i].clone(); } } } + +/// Quaternion extension +extension QuaternionExtension on Quaternion { + /// To [Vector3] + Vector3 toPoint() => Vector3(x, y, z); + + /// Normalize except w + Quaternion ewNormalize() { + var norm = sqrt(x * x + y * y + z * z); + return Quaternion(x / norm, y / norm, z / norm, 0); + } + + /// Rotate with rad for point + Quaternion wpRotate(double rad) { + var r = rad / 2, w = cos(r), k = sin(r); + return Quaternion(k * x, k * y, k * z, w); + } + + /// Get text to display + String getText() { + return 'x: $x, y: $y, z: $z, w: $w'; + } +} From 03a7a6dea211cb73658b1414b6ea34ca959877c0 Mon Sep 17 00:00:00 2001 From: Dynesshely Date: Sat, 30 Mar 2024 16:08:51 +0800 Subject: [PATCH 2/6] =?UTF-8?q?=F0=9F=8E=87=20Style:=20Refactor=20rotation?= =?UTF-8?q?=5Femulator.dart=20for=20clarity=20and=20readability?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lib/utils/emulators/rotation_emulator.dart | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/kitx_mobile/lib/utils/emulators/rotation_emulator.dart b/kitx_mobile/lib/utils/emulators/rotation_emulator.dart index 278ea9c..635ca95 100644 --- a/kitx_mobile/lib/utils/emulators/rotation_emulator.dart +++ b/kitx_mobile/lib/utils/emulators/rotation_emulator.dart @@ -50,8 +50,8 @@ class DeviceRotationHost { /// Points to rotate static List points = [], originPoints = []; - /// 显式调用此方法来计算加速度之后的四元数 - /// [gyroX], [gyroY], [gyroZ] 单位: rad/s, [samplingRate] 单位: s + /// Explicitly call this method to calculate the quaternion after rotation + /// [gyroX], [gyroY], [gyroZ] unit: rad/s, [samplingRate] unit: s static void rotateWithAcceleration(double gyroX, double gyroY, double gyroZ, double samplingRate) { var radX = gyroX * samplingRate, radY = gyroY * samplingRate, radZ = gyroZ * samplingRate; @@ -96,20 +96,15 @@ extension QuaternionExtension on Quaternion { /// To [Vector3] Vector3 toPoint() => Vector3(x, y, z); - /// Normalize except w + /// Normalize without arg w Quaternion ewNormalize() { var norm = sqrt(x * x + y * y + z * z); return Quaternion(x / norm, y / norm, z / norm, 0); } - /// Rotate with rad for point + /// Rotate direction with rad Quaternion wpRotate(double rad) { var r = rad / 2, w = cos(r), k = sin(r); return Quaternion(k * x, k * y, k * z, w); } - - /// Get text to display - String getText() { - return 'x: $x, y: $y, z: $z, w: $w'; - } } From 2881c29eafda48cabed77a876a282c8e6b91bebd Mon Sep 17 00:00:00 2001 From: Dynesshely Date: Sat, 30 Mar 2024 18:48:21 +0800 Subject: [PATCH 3/6] =?UTF-8?q?=F0=9F=92=BE=20Feat(Rotation):=20Better=20U?= =?UTF-8?q?I?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../acceleration_display_stand.dart | 152 +++++++++++++++++- .../gyroscope_display_stand.dart | 28 ++-- .../utils/emulators/rotation_emulator.dart | 4 + kitx_mobile/pubspec.lock | 8 + kitx_mobile/pubspec.yaml | 1 + 5 files changed, 172 insertions(+), 21 deletions(-) diff --git a/kitx_mobile/lib/pages/test_pages/sensors_display_stands/acceleration_display_stand.dart b/kitx_mobile/lib/pages/test_pages/sensors_display_stands/acceleration_display_stand.dart index 9974725..8b6972a 100644 --- a/kitx_mobile/lib/pages/test_pages/sensors_display_stands/acceleration_display_stand.dart +++ b/kitx_mobile/lib/pages/test_pages/sensors_display_stands/acceleration_display_stand.dart @@ -1,5 +1,7 @@ import 'dart:async'; +import 'dart:collection'; +import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:sensors_plus/sensors_plus.dart'; @@ -15,16 +17,46 @@ class AccelerationDisplayStandState extends State { /// Acceleration x-axis, y-axis, z-axis final accX = 0.0.obs, accY = 0.0.obs, accZ = 0.0.obs; + /// Sampling Rage + var samplingRate = 0.05.obs; + /// User accelerometer sensor data listener StreamSubscription? userAccelerometerDataListener; + /// Is listener paused + var listenerPaused = false.obs; + + /// Chart related data + var minX = 0.0.obs, maxX = 0.05.obs, xCount = 10.obs; + + /// Acceleration x-axis, y-axis, z-axis values + var xValues = [], yValues = [], zValues = []; + @override void initState() { - userAccelerometerDataListener = userAccelerometerEventStream(samplingPeriod: Duration(milliseconds: 50)).listen((event) { - accX.value = event.x; - accY.value = event.y; - accZ.value = event.z; - }); + userAccelerometerDataListener = userAccelerometerEventStream( + samplingPeriod: Duration(milliseconds: (samplingRate * 1000).toInt()), + ).listen( + (event) { + accX.value = event.x; + accY.value = event.y; + accZ.value = event.z; + maxX.value += samplingRate.value; + if ((maxX.value - minX.value) >= samplingRate.value * xCount.value) { + minX.value += samplingRate.value; + } + xValues.add(FlSpot(maxX.value, event.x)); + yValues.add(FlSpot(maxX.value, event.y)); + zValues.add(FlSpot(maxX.value, event.z)); + if (xValues.length > xCount.value + 1) { + xValues.removeAt(0); + yValues.removeAt(0); + zValues.removeAt(0); + } + }, + onError: (error) {}, + cancelOnError: true, + ); super.initState(); } @@ -42,14 +74,118 @@ class AccelerationDisplayStandState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Acceleration Data', style: TextStyle(fontSize: 32)), + Container( + height: 300, + margin: EdgeInsets.fromLTRB(0, 30, 20, 30), + child: Obx( + () => LineChart( + LineChartData( + minX: minX.value, + maxX: maxX.value, + minY: -5, + maxY: 5, + lineBarsData: [ + LineChartBarData( + spots: xValues, + color: Colors.redAccent, + isStrokeCapRound: true, + isStrokeJoinRound: true, + ), + LineChartBarData( + spots: yValues, + color: Colors.greenAccent, + isStrokeCapRound: true, + isStrokeJoinRound: true, + ), + LineChartBarData( + spots: zValues, + color: Colors.blueAccent, + isStrokeCapRound: true, + isStrokeJoinRound: true, + ), + ], + titlesData: FlTitlesData( + show: true, + rightTitles: const AxisTitles( + sideTitles: SideTitles(showTitles: false), + ), + topTitles: const AxisTitles( + sideTitles: SideTitles(showTitles: false), + ), + bottomTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + reservedSize: 40, + interval: 1, + getTitlesWidget: (a, b) => Padding( + padding: EdgeInsets.only(top: 10), + child: Text(a.toStringAsFixed(3).toString()), + ), + ), + ), + leftTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + interval: 1, + getTitlesWidget: (a, b) => Text(a.toInt().toString()), + reservedSize: 30, + ), + ), + ), + gridData: FlGridData( + show: true, + drawVerticalLine: true, + horizontalInterval: 1, + verticalInterval: samplingRate.value, + getDrawingHorizontalLine: (value) { + return const FlLine( + color: Colors.indigo, + strokeWidth: 1, + ); + }, + getDrawingVerticalLine: (value) { + return const FlLine( + color: Colors.indigo, + strokeWidth: 1, + ); + }, + ), + borderData: FlBorderData( + show: true, + border: Border.all(color: Colors.indigo), + ), + ), + duration: const Duration(milliseconds: 0), + ), + ), + ), + Obx( + () => Text('${accX > 0 ? "⏪" : "⏩"} \tx: ${accX.value}', style: TextStyle(fontSize: 16, color: Colors.redAccent)), + ), + Obx( + () => Text('${accY > 0 ? "⏬" : "⏫"} \ty: ${accY.value}', style: TextStyle(fontSize: 16, color: Colors.greenAccent)), + ), Obx( - () => Text('x: ${accX.value}', style: TextStyle(fontSize: 14)), + () => Text('${accZ > 0 ? "⬇" : "⬆"} \tz: ${accZ.value}', style: TextStyle(fontSize: 16, color: Colors.blueAccent)), ), Obx( - () => Text('y: ${accY.value}', style: TextStyle(fontSize: 14)), + () => Text('⏱ \tSampling Rate: ${samplingRate.value} s', style: TextStyle(fontSize: 16)), ), + const Text('↔ \tUnit: m/s^2', style: TextStyle(fontSize: 16)), + const SizedBox(height: 20), Obx( - () => Text('z: ${accZ.value}', style: TextStyle(fontSize: 14)), + () => ElevatedButton( + onPressed: () { + if (userAccelerometerDataListener?.isPaused ?? true) { + userAccelerometerDataListener?.resume(); + listenerPaused.value = false; + } else { + userAccelerometerDataListener?.pause(); + listenerPaused.value = true; + } + }, + child: listenerPaused.value ? const Text('Resume') : const Text("Pause"), + ), ), ], ), diff --git a/kitx_mobile/lib/pages/test_pages/sensors_display_stands/gyroscope_display_stand.dart b/kitx_mobile/lib/pages/test_pages/sensors_display_stands/gyroscope_display_stand.dart index 146e1ea..6431df3 100644 --- a/kitx_mobile/lib/pages/test_pages/sensors_display_stands/gyroscope_display_stand.dart +++ b/kitx_mobile/lib/pages/test_pages/sensors_display_stands/gyroscope_display_stand.dart @@ -17,15 +17,15 @@ class GyroscopeDisplayStandState extends State { /// Gyroscope x-axis, y-axis, z-axis final dirX = 0.0.obs, dirY = 0.0.obs, dirZ = 0.0.obs; - /// Gyroscope direction x, y, z - final directionX = 'none'.obs, directionY = 'none'.obs, directionZ = 'none'.obs; - /// Drawing canvas size static double canvasWidth = 400, canvasHeight = 300; /// Is drawing paused var rotationPaused = false.obs; + /// Sampling Rage + var samplingRate = 0.05.obs; + /// Gyroscope sensor data listener StreamSubscription? gyroscopeDataListener; @@ -36,21 +36,19 @@ class GyroscopeDisplayStandState extends State { void initState() { painter.initialize(); - gyroscopeDataListener = gyroscopeEventStream(samplingPeriod: Duration(milliseconds: 50)).listen( + gyroscopeDataListener = gyroscopeEventStream( + samplingPeriod: Duration(milliseconds: (samplingRate.value * 1000).toInt()), + ).listen( (event) { - DeviceRotationHost.rotateWithAcceleration(event.x, event.y, event.z, 0.05); + DeviceRotationHost.rotateWithAcceleration(event.x, event.y, event.z, samplingRate.value); dirX.value = event.x; dirY.value = event.y; dirZ.value = event.z; - - directionX.value = dirX >= 0 ? '⇊' : '⇈'; - directionY.value = dirY >= 0 ? '↻' : '↺'; - directionZ.value = dirZ >= 0 ? '↶' : '↷'; }, onError: (error) { Timer.periodic(Duration(milliseconds: 50), (timer) { - DeviceRotationHost.rotateWithAcceleration(0, 0, 0.5, 0.05); + DeviceRotationHost.rotateWithAcceleration(0.5, 0.5, 0.5, samplingRate.value); }); }, cancelOnError: true, @@ -122,14 +120,18 @@ class GyroscopeDisplayStandState extends State { ], ), Obx( - () => Text('${directionX.value} x: ${dirX.value}', style: TextStyle(fontSize: 16)), + () => Text('${dirX >= 0 ? '⏬' : '⏫'} \tx: ${dirX.value}', style: TextStyle(fontSize: 16)), + ), + Obx( + () => Text('${dirY >= 0 ? '⤵' : '⤴'} \ty: ${dirY.value}', style: TextStyle(fontSize: 16)), ), Obx( - () => Text('${directionY.value} y: ${dirY.value}', style: TextStyle(fontSize: 16)), + () => Text('${dirZ >= 0 ? '⏪' : '⏩'} \tz: ${dirZ.value}', style: TextStyle(fontSize: 16)), ), Obx( - () => Text('${directionZ.value} z: ${dirZ.value}', style: TextStyle(fontSize: 16)), + () => Text('⏱ \tSampling Rate: ${samplingRate.value} s', style: TextStyle(fontSize: 16)), ), + const Text('↔ \tUnit: rad/s', style: TextStyle(fontSize: 16)), ], ), ); diff --git a/kitx_mobile/lib/utils/emulators/rotation_emulator.dart b/kitx_mobile/lib/utils/emulators/rotation_emulator.dart index 635ca95..bbe5982 100644 --- a/kitx_mobile/lib/utils/emulators/rotation_emulator.dart +++ b/kitx_mobile/lib/utils/emulators/rotation_emulator.dart @@ -70,6 +70,10 @@ class DeviceRotationHost { np = zDirR * np * zDirRInv; points[i] = np; } + + lastXDirR = xDirR; + lastYDirR = yDirR; + lastZDirR = zDirR; } /// Set points diff --git a/kitx_mobile/pubspec.lock b/kitx_mobile/pubspec.lock index 471da4b..52697ba 100644 --- a/kitx_mobile/pubspec.lock +++ b/kitx_mobile/pubspec.lock @@ -265,6 +265,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + fl_chart: + dependency: "direct main" + description: + name: fl_chart + sha256: "2b7c1f5d867da9a054661641c8f499c55c47c39acccb97b3bc673f5fa9a39e74" + url: "https://pub.dev" + source: hosted + version: "0.67.0" flutter: dependency: "direct main" description: flutter diff --git a/kitx_mobile/pubspec.yaml b/kitx_mobile/pubspec.yaml index b9756df..29cf925 100644 --- a/kitx_mobile/pubspec.yaml +++ b/kitx_mobile/pubspec.yaml @@ -17,6 +17,7 @@ dependencies: crypto: ^3.0.3 device_info_plus: ^9.1.1 f_logs: ^2.0.1 + fl_chart: ^0.67.0 flutter: sdk: flutter flutter_blue_plus: ^1.31.15 From 4d1965aac72e1fd604852b05958db7d78161e0f9 Mon Sep 17 00:00:00 2001 From: Dynesshely Date: Sat, 30 Mar 2024 18:52:06 +0800 Subject: [PATCH 4/6] =?UTF-8?q?=F0=9F=92=BE=20Feat(Acceleration):=20Provid?= =?UTF-8?q?e=20data=20generater=20when=20no=20sensor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../acceleration_display_stand.dart | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/kitx_mobile/lib/pages/test_pages/sensors_display_stands/acceleration_display_stand.dart b/kitx_mobile/lib/pages/test_pages/sensors_display_stands/acceleration_display_stand.dart index 8b6972a..7fb55f7 100644 --- a/kitx_mobile/lib/pages/test_pages/sensors_display_stands/acceleration_display_stand.dart +++ b/kitx_mobile/lib/pages/test_pages/sensors_display_stands/acceleration_display_stand.dart @@ -1,5 +1,5 @@ import 'dart:async'; -import 'dart:collection'; +import 'dart:math'; import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; @@ -54,7 +54,26 @@ class AccelerationDisplayStandState extends State { zValues.removeAt(0); } }, - onError: (error) {}, + onError: (error) { + var random = Random(114514); + Timer.periodic(Duration(milliseconds: (samplingRate * 1000).toInt()), (timer) { + accX.value = random.nextDouble() * 10 - 5; + accY.value = random.nextDouble() * 10 - 5; + accZ.value = random.nextDouble() * 10 - 5; + maxX.value += samplingRate.value; + if ((maxX.value - minX.value) >= samplingRate.value * xCount.value) { + minX.value += samplingRate.value; + } + xValues.add(FlSpot(maxX.value, accX.value)); + yValues.add(FlSpot(maxX.value, accY.value)); + zValues.add(FlSpot(maxX.value, accZ.value)); + if (xValues.length > xCount.value + 1) { + xValues.removeAt(0); + yValues.removeAt(0); + zValues.removeAt(0); + } + }); + }, cancelOnError: true, ); super.initState(); From 8eeb9b138fc7878cfaea35645100dc7cb68a41d7 Mon Sep 17 00:00:00 2001 From: Dynesshely Date: Sat, 30 Mar 2024 19:26:04 +0800 Subject: [PATCH 5/6] =?UTF-8?q?=F0=9F=92=BE=20Feat(Sensor):=20Update=20sen?= =?UTF-8?q?sor=20data=20handling=20and=20error=20messages?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 6 ++-- .../acceleration_display_stand.dart | 33 +++++++++++-------- .../gyroscope_display_stand.dart | 16 +++++++-- 3 files changed, 37 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 064988d..e8d46d5 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,11 @@ Runs on `Android`, `iOS` > **Warning** > -> Please use physical devices to debug, any emulator may cause problems. +> We recommend that you use a physical device for debugging, you may experience some issues debugging on an emulator > -> Android emulators are currently known to experience problems; the situation with iOS simulators is currently unknown. +> Known issues are as follows: +> 1. KitX Mobile will join the LAN multicast. If the emulator does not support this function, an error will occur. +> 2. The simulator does not provide sensor data, KitX Mobile will use random data to replace scenarios that require sensor data. ### Prerequisites diff --git a/kitx_mobile/lib/pages/test_pages/sensors_display_stands/acceleration_display_stand.dart b/kitx_mobile/lib/pages/test_pages/sensors_display_stands/acceleration_display_stand.dart index 7fb55f7..bc72f0d 100644 --- a/kitx_mobile/lib/pages/test_pages/sensors_display_stands/acceleration_display_stand.dart +++ b/kitx_mobile/lib/pages/test_pages/sensors_display_stands/acceleration_display_stand.dart @@ -23,8 +23,8 @@ class AccelerationDisplayStandState extends State { /// User accelerometer sensor data listener StreamSubscription? userAccelerometerDataListener; - /// Is listener paused - var listenerPaused = false.obs; + /// Is listener paused, errored + var listenerPaused = false.obs, listnerErrored = false.obs; /// Chart related data var minX = 0.0.obs, maxX = 0.05.obs, xCount = 10.obs; @@ -55,7 +55,10 @@ class AccelerationDisplayStandState extends State { } }, onError: (error) { + listnerErrored.value = true; + var random = Random(114514); + Timer.periodic(Duration(milliseconds: (samplingRate * 1000).toInt()), (timer) { accX.value = random.nextDouble() * 10 - 5; accY.value = random.nextDouble() * 10 - 5; @@ -193,18 +196,20 @@ class AccelerationDisplayStandState extends State { const Text('↔ \tUnit: m/s^2', style: TextStyle(fontSize: 16)), const SizedBox(height: 20), Obx( - () => ElevatedButton( - onPressed: () { - if (userAccelerometerDataListener?.isPaused ?? true) { - userAccelerometerDataListener?.resume(); - listenerPaused.value = false; - } else { - userAccelerometerDataListener?.pause(); - listenerPaused.value = true; - } - }, - child: listenerPaused.value ? const Text('Resume') : const Text("Pause"), - ), + () => listnerErrored.value + ? const Text('No sensor data, you are seeing random data.') + : ElevatedButton( + onPressed: () { + if (userAccelerometerDataListener?.isPaused ?? true) { + userAccelerometerDataListener?.resume(); + listenerPaused.value = false; + } else { + userAccelerometerDataListener?.pause(); + listenerPaused.value = true; + } + }, + child: listenerPaused.value ? const Text('Resume') : const Text("Pause"), + ), ), ], ), diff --git a/kitx_mobile/lib/pages/test_pages/sensors_display_stands/gyroscope_display_stand.dart b/kitx_mobile/lib/pages/test_pages/sensors_display_stands/gyroscope_display_stand.dart index 6431df3..84f295d 100644 --- a/kitx_mobile/lib/pages/test_pages/sensors_display_stands/gyroscope_display_stand.dart +++ b/kitx_mobile/lib/pages/test_pages/sensors_display_stands/gyroscope_display_stand.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:math'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; @@ -21,7 +22,8 @@ class GyroscopeDisplayStandState extends State { static double canvasWidth = 400, canvasHeight = 300; /// Is drawing paused - var rotationPaused = false.obs; + /// Is listener errored + var rotationPaused = false.obs, listenerErrored = false.obs; /// Sampling Rage var samplingRate = 0.05.obs; @@ -47,8 +49,16 @@ class GyroscopeDisplayStandState extends State { dirZ.value = event.z; }, onError: (error) { + listenerErrored.value = true; + + var random = Random(114514); + Timer.periodic(Duration(milliseconds: 50), (timer) { - DeviceRotationHost.rotateWithAcceleration(0.5, 0.5, 0.5, samplingRate.value); + var rad = 0.5 + random.nextDouble() / 10; + + dirY.value = rad; + + DeviceRotationHost.rotateWithAcceleration(dirX.value, dirY.value, dirZ.value, samplingRate.value); }); }, cancelOnError: true, @@ -132,6 +142,8 @@ class GyroscopeDisplayStandState extends State { () => Text('⏱ \tSampling Rate: ${samplingRate.value} s', style: TextStyle(fontSize: 16)), ), const Text('↔ \tUnit: rad/s', style: TextStyle(fontSize: 16)), + const SizedBox(height: 20), + Obx(() => listenerErrored.value ? const Text('No sensor data, you are seeing random data.') : const SizedBox()) ], ), ); From c5837267058eb424efd3f3b69d9d48ffc319292b Mon Sep 17 00:00:00 2001 From: Dynesshely Date: Sat, 30 Mar 2024 19:29:23 +0800 Subject: [PATCH 6/6] =?UTF-8?q?=F0=9F=94=A7=20Fix(Sensor):=20Random=20sens?= =?UTF-8?q?or=20data=20timer=20cancellation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sensors_display_stands/acceleration_display_stand.dart | 6 +++++- .../sensors_display_stands/gyroscope_display_stand.dart | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/kitx_mobile/lib/pages/test_pages/sensors_display_stands/acceleration_display_stand.dart b/kitx_mobile/lib/pages/test_pages/sensors_display_stands/acceleration_display_stand.dart index bc72f0d..4fa0f73 100644 --- a/kitx_mobile/lib/pages/test_pages/sensors_display_stands/acceleration_display_stand.dart +++ b/kitx_mobile/lib/pages/test_pages/sensors_display_stands/acceleration_display_stand.dart @@ -32,6 +32,9 @@ class AccelerationDisplayStandState extends State { /// Acceleration x-axis, y-axis, z-axis values var xValues = [], yValues = [], zValues = []; + /// Random sensor data timer + Timer? randomSensorDataTimer; + @override void initState() { userAccelerometerDataListener = userAccelerometerEventStream( @@ -59,7 +62,7 @@ class AccelerationDisplayStandState extends State { var random = Random(114514); - Timer.periodic(Duration(milliseconds: (samplingRate * 1000).toInt()), (timer) { + randomSensorDataTimer = Timer.periodic(Duration(milliseconds: (samplingRate * 1000).toInt()), (timer) { accX.value = random.nextDouble() * 10 - 5; accY.value = random.nextDouble() * 10 - 5; accZ.value = random.nextDouble() * 10 - 5; @@ -85,6 +88,7 @@ class AccelerationDisplayStandState extends State { @override void dispose() { userAccelerometerDataListener?.cancel(); + randomSensorDataTimer?.cancel(); super.dispose(); } diff --git a/kitx_mobile/lib/pages/test_pages/sensors_display_stands/gyroscope_display_stand.dart b/kitx_mobile/lib/pages/test_pages/sensors_display_stands/gyroscope_display_stand.dart index 84f295d..5d4769c 100644 --- a/kitx_mobile/lib/pages/test_pages/sensors_display_stands/gyroscope_display_stand.dart +++ b/kitx_mobile/lib/pages/test_pages/sensors_display_stands/gyroscope_display_stand.dart @@ -34,6 +34,9 @@ class GyroscopeDisplayStandState extends State { /// Painter Painter painter = Painter(); + /// Random sensor data timer + Timer? randomSensorDataTimer; + @override void initState() { painter.initialize(); @@ -53,7 +56,7 @@ class GyroscopeDisplayStandState extends State { var random = Random(114514); - Timer.periodic(Duration(milliseconds: 50), (timer) { + randomSensorDataTimer = Timer.periodic(Duration(milliseconds: 50), (timer) { var rad = 0.5 + random.nextDouble() / 10; dirY.value = rad; @@ -70,6 +73,7 @@ class GyroscopeDisplayStandState extends State { @override void dispose() { gyroscopeDataListener?.cancel(); + randomSensorDataTimer?.cancel(); super.dispose(); }