Skip to content

Commit

Permalink
Merge pull request #62 from OpenEarable/main-unsigned-CLA
Browse files Browse the repository at this point in the history
Merge apps for which the CLA is now signed
  • Loading branch information
o-bagge authored Feb 20, 2024
2 parents 1db0ef6 + de9788c commit 3ffde0b
Show file tree
Hide file tree
Showing 9 changed files with 476 additions and 77 deletions.
Binary file added open_earable/assets/powernapper/198155.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
144 changes: 67 additions & 77 deletions open_earable/lib/apps/jump_height_test/jump_height_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'dart:async';
import 'package:open_earable_flutter/src/open_earable_flutter.dart';
import 'package:simple_kalman/simple_kalman.dart';
import 'dart:math';
import '../../widgets/earable_not_connected_warning.dart';

/// An app that lets you test your jump height using an OpenEarable device.
class JumpHeightTest extends StatefulWidget {
Expand All @@ -19,43 +20,58 @@ class JumpHeightTest extends StatefulWidget {

/// State class for JumpHeightTest widget.
class _JumpHeightTestState extends State<JumpHeightTest>
with SingleTickerProviderStateMixin {
with SingleTickerProviderStateMixin {
/// Stores the start time of a jump test.
DateTime? _startOfJump;

/// Stores the duration of a jump test.
Duration _jumpDuration = Duration.zero;

/// Current height calculated from sensor data.
double _height = 0.0;
// List to store each jump's data.
List<Jump> _jumpData = [];
// Flag to indicate if jump measurement is ongoing.
bool _isJumping = false;

/// Instance of OpenEarable device.
final OpenEarable _openEarable;

/// Flag to indicate if an OpenEarable device is connected.
bool _earableConnected = false;

/// Subscription to IMU sensor data.
StreamSubscription? _imuSubscription;

/// Stores the maximum height achieved in a jump.
double _maxHeight = 0.0; // Variable to keep track of maximum jump height
double _maxHeight = 0.0; // Variable to keep track of maximum jump height
/// Error measure for Kalman filter.
final _errorMeasureAcc = 5.0;

/// Kalman filters for accelerometer data.
late SimpleKalman _kalmanX, _kalmanY, _kalmanZ;

/// Current velocity calculated from acceleration.
double _velocity = 0.0;

/// Sampling rate time slice (inverse of frequency).
double _timeSlice = 1 / 30.0;
double _timeSlice = 1 / 30.0;

/// Standard gravity in m/s^2.
double _gravity = 9.81;

/// X-axis acceleration.
double _accX = 0.0;

/// Y-axis acceleration.
double _accY = 0.0;

/// Z-axis acceleration.
double _accZ = 0.0;

/// Pitch angle in radians.
double _pitch = 0.0;

/// Manages the [TabBar].
late TabController _tabController;

Expand Down Expand Up @@ -88,18 +104,18 @@ class _JumpHeightTestState extends State<JumpHeightTest>
/// Sets up listeners to receive sensor data from the OpenEarable device.
_setupListeners() {
_imuSubscription =
_openEarable.sensorManager.subscribeToSensorData(0).listen((data) {
// Only process sensor data if jump measurement is ongoing.
if (!_isJumping) {
return;
}
setState(() {
_jumpDuration = DateTime.now().difference(_startOfJump!);
});
_processSensorData(data);
_openEarable.sensorManager.subscribeToSensorData(0).listen((data) {
// Only process sensor data if jump measurement is ongoing.
if (!_isJumping) {
return;
}
setState(() {
_jumpDuration = DateTime.now().difference(_startOfJump!);
});
_processSensorData(data);
});
}

/// Starts the jump height measurement process.
/// It sets the sampling rate, initializes or resets variables, and begins listening to sensor data.
void _startJump() {
Expand Down Expand Up @@ -145,25 +161,29 @@ class _JumpHeightTestState extends State<JumpHeightTest>
void _processSensorData(Map<String, dynamic> data) {
/// Kalman filtered accelerometer data for X.
_accX = _kalmanX.filtered(data["ACC"]["X"]);

/// Kalman filtered accelerometer data for Y.
_accY = _kalmanY.filtered(data["ACC"]["Y"]);

/// Kalman filtered accelerometer data for Z.
_accZ = _kalmanZ.filtered(data["ACC"]["Z"]);

/// Pitch angle in radians.
_pitch = data["EULER"]["PITCH"];
// Calculates the current vertical acceleration.
// It adjusts the Z-axis acceleration with the pitch angle to account for the device's orientation.
double currentAcc = _accZ * cos(_pitch) + _accX * sin(_pitch);
// Subtract gravity to get acceleration due to movement.
currentAcc -= _gravity;
currentAcc -= _gravity;

_updateHeight(currentAcc);
}

/// Checks if the device is stationary based on acceleration magnitude.
bool _deviceIsStationary(double threshold) {
double accMagnitude = sqrt(_accX * _accX + _accY * _accY + _accZ * _accZ);
bool isStationary = (accMagnitude > _gravity - threshold) && (accMagnitude < _gravity + threshold);
bool isStationary = (accMagnitude > _gravity - threshold) &&
(accMagnitude < _gravity + threshold);
return isStationary;
}

Expand All @@ -173,14 +193,14 @@ class _JumpHeightTestState extends State<JumpHeightTest>
_updateHeight(double currentAcc) {
setState(() {
if (_deviceIsStationary(0.3)) {
_velocity = 0.0;
_height = 0.0;
_velocity = 0.0;
_height = 0.0;
} else {
// Integrate acceleration to get velocity.
_velocity += currentAcc * _timeSlice;
// Integrate acceleration to get velocity.
_velocity += currentAcc * _timeSlice;

// Integrate velocity to get height.
_height += _velocity * _timeSlice;
// Integrate velocity to get height.
_height += _velocity * _timeSlice;
}

// Prevent height from going negative.
Expand All @@ -199,7 +219,7 @@ class _JumpHeightTestState extends State<JumpHeightTest>

String _prettyDuration(Duration duration) {
var seconds = duration.inMilliseconds / 1000;
return '${seconds.toStringAsFixed(2)} s';
return '${seconds.toStringAsFixed(2)} s';
}

/// Builds the UI for the jump height test.
Expand All @@ -218,7 +238,8 @@ class _JumpHeightTestState extends State<JumpHeightTest>
controller: _tabController,
indicatorColor: Colors.white, // Color of the underline indicator
labelColor: Colors.white, // Color of the active tab label
unselectedLabelColor: Colors.grey, // Color of the inactive tab labels
unselectedLabelColor:
Colors.grey, // Color of the inactive tab labels
tabs: [
Tab(text: 'Height'),
Tab(text: 'Raw Acc.'),
Expand All @@ -227,73 +248,42 @@ class _JumpHeightTestState extends State<JumpHeightTest>
),
Expanded(
child: (!_openEarable.bleManager.connected)
? _notConnectedWidget()
? EarableNotConnectedWarning()
: _buildJumpHeightDataTabs(),
),
SizedBox(height: 20), // Margin between chart and button
SizedBox(height: 20), // Margin between chart and button
_buildButtons(),
Visibility(
// Show error message if no OpenEarable device is connected.
visible: !_earableConnected,
maintainState: true,
maintainAnimation: true,
child: Text(
"No Earable Connected",
style: TextStyle(
color: Colors.red,
fontSize: 12,
),
// Show error message if no OpenEarable device is connected.
visible: !_earableConnected,
maintainState: true,
maintainAnimation: true,
child: Text(
"No Earable Connected",
style: TextStyle(
color: Colors.red,
fontSize: 12,
),
),
SizedBox(height: 20), // Margin between button and text
),
SizedBox(height: 20), // Margin between button and text
_buildText()
],
),
);
}

Widget _notConnectedWidget() {
return Stack(
fit: StackFit.expand,
Widget _buildJumpHeightDataTabs() {
return TabBarView(
controller: _tabController,
children: [
Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.warning,
size: 48,
color: Colors.red,
),
SizedBox(height: 16),
Center(
child: Text(
"Not connected to\nOpenEarable device",
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
),
),
],
),
),
JumpHeightChart(_openEarable, "Height Data"),
JumpHeightChart(_openEarable, "Raw Acceleration Data"),
JumpHeightChart(_openEarable, "Filtered Acceleration Data")
],
);
}

Widget _buildJumpHeightDataTabs() {
return TabBarView(
controller: _tabController,
children: [
JumpHeightChart(_openEarable, "Height Data"),
JumpHeightChart(_openEarable, "Raw Acceleration Data"),
JumpHeightChart(_openEarable, "Filtered Acceleration Data")
],
);
}

Widget _buildText() {
return Container(
child: Column(
Expand All @@ -302,15 +292,15 @@ class _JumpHeightTestState extends State<JumpHeightTest>
'Max height: ${_maxHeight.toStringAsFixed(2)} m',
style: Theme.of(context).textTheme.headlineMedium,
),
Text('Jump time: ${_prettyDuration(_jumpDuration)}',
Text(
'Jump time: ${_prettyDuration(_jumpDuration)}',
style: Theme.of(context).textTheme.headlineSmall,
),
],
),
);
}


/// Builds buttons to start and stop the jump height measurement process.
Widget _buildButtons() {
return Flexible(
Expand All @@ -328,7 +318,7 @@ class _JumpHeightTestState extends State<JumpHeightTest>
),
);
}

/// Builds a sensor configuration for the OpenEarable device.
/// Sets the sensor ID, sampling rate, and latency.
OpenEarableSensorConfig _buildSensorConfig() {
Expand Down
Loading

0 comments on commit 3ffde0b

Please sign in to comment.