Skip to content

nth-solutions/JavaDashboardMaster

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

BioForce Technical Documentation

This document contains explanations of various technical details regarding the process of reading data from a module, organizing and manipulating it, as well as various standards used throughout the project.

Table of Contents

Development Information

The BioForce project uses Maven for generating project files, building the codebase, and packaging into executables. Launch4J, which builds .exes, is configured entirely in pom.xml and is bundled with Oracle's Java 8 for compatibility. Windows installers are generated by NSIS using the script tools/install.nsi.

The Dashboard may have compatibility issues with different versions of Java 8, so when in doubt, use Oracle JDK 8u202.

GitHub Actions is used for CI/CD via Maven to automatically run tests, build executables, and publish releases when code is pushed to the repository. The jobs that are run can be configured in .github/workflows.

Unit tests are located at src/test and should be written incrementally to self-check the codebase when adding new features.

Data Formats

SerialComm handles all communications between the computer and the module over a USB serial port.

Test Data

A data sample is stored in the module as a 16-bit unsigned integer, meaning a value between 0 and 65535. Each data sample is composed of two bytes (2 * 8 bits in a byte = 16 bits).

Example: ax1 ax2 are the two bytes composing a single acceleration data sample for the X axis.

To convert from byte format to integer format, multiply the first byte by 256 and then add the second byte.

Example: ax = ax1 * 256 + ax2.

The bytes are stored chronologically in the finalData array in the following order, with all samples in a given row being from the same point in time:

Data Sample # Acceleration X Acceleration Y Acceleration Z Gyroscope X Gyroscope Y Gyroscope Z Magnetometer X Magnetometer Y Magnetometer Z
1 ax1 ax2 ay1 ay2 az1 az2 gx1 gx2 gy1 gy2 gz1 gz2 mx1 mx2 my1 my2 mz1 mz2
2 ax1 ax2 ay1 ay2 az1 az2 gx1 gx2 gy1 gy2 gz1 gz2
3 ax1 ax2 ay1 ay2 az1 az2 gx1 gx2 gy1 gy2 gz1 gz2
4 ax1 ax2 ay1 ay2 az1 az2 gx1 gx2 gy1 gy2 gz1 gz2
5 ax1 ax2 ay1 ay2 az1 az2 gx1 gx2 gy1 gy2 gz1 gz2
6 ax1 ax2 ay1 ay2 az1 az2 gx1 gx2 gy1 gy2 gz1 gz2
7 ax1 ax2 ay1 ay2 az1 az2 gx1 gx2 gy1 gy2 gz1 gz2
8 ax1 ax2 ay1 ay2 az1 az2 gx1 gx2 gy1 gy2 gz1 gz2
9 ax1 ax2 ay1 ay2 az1 az2 gx1 gx2 gy1 gy2 gz1 gz2
10 ax1 ax2 ay1 ay2 az1 az2 gx1 gx2 gy1 gy2 gz1 gz2
11 ax1 ax2 ay1 ay2 az1 az2 gx1 gx2 gy1 gy2 gz1 gz2 mx1 mx2 my1 my2 mz1 mz2
... ... ... ... ... ... ...

Because the magnetometer has 1/10th the sample rate of the accelerometer and gyroscope (when the latter is 240 Hz or greater), it only has data for every 10th point on the time axis.

Test Parameters

The test parameters associated with a test's data is stored in a .CSVP file as a series of integers separated by newlines. This .CSVP file is a custom file extension and technically is not a "comma separated" file, but this design decision is an artifact carried on from the original DataOrganizer codebase.

The .CSVP file is always 32 lines long, being padded with 0s and newlines after the test parameters to reach this length.

The format of List<Integer> testParameters is shown below:

  1. Number of Tests (0-8)
  2. Timer0 Tick Threshold (default 3848)
  3. Delay After Start (milliseconds) - default 0
  4. Battery Timeout Length (seconds) - default 300
  5. Timed Test Flag (0/1) - default 0 - limits maximum test length to "Test Duration" field
  6. Trigger on Release Flag (0/1) - default 1 - allows test to start when the remote button is released vs when pressed
  7. Test Duration (seconds) - default 30 - only applicable if "Timed Test Flag" is set to 1
  8. Accel/Gyro Sample Rate (60/120/240/480/500/960 Hz) - default 960
  9. Mag Sample Rate (Hz) - default 96 - If "Accel/Gyro sample rate" ≥ 240, mag is 1/10 of it; otherwise, sample rate is equal
  10. Accel Sensitivity (2/4/8/16 Gs) - default 4
  11. Gyro Sensitivity (250/500/1000/2000 deg/s) - default 1000
  12. Accel Filter (5/10/20/41/92/184/460/1130 (OFF) Hz) - default 92
  13. Gyro Filter (10/20/41/92/184/250/3600/8800 (OFF) Hz) - default 92
  14. Accel X Offset Min
  15. Accel X Offset Max
  16. Accel Y Offset Min
  17. Accel Y Offset Max
  18. Accel Z Offset Min
  19. Accel Z Offset Max

The Java representation of the test parameters, List<Integer> testParameters, only has elements 0-12, with 13-18, the acceleration offsets, being appended on when writing the data to a .CSVP file.

Acceleration Offsets

Indices 13-18 of testParameters collectively make up the Inertial Measurement Unit (IMU) calibration offsets. These values are signed raw data samples of the accelerometer on each axis when the module is at rest, laying on a flat surface. The maximum and minimum values correspond to the two orientations the axis can be (facing up vs facing down). Averaging these two offsets for an axis and subtracting the result from each acceleration data sample "normalizes" the value to zero, counteracts any deviation in IMU measurements.

Note: this is NOT the same as data normalization, which is used for gravity compensation when the module is moving on only one axis.

In the codebase, these offsets are in the form of an int[] of length 9, referred to as mpuOffsets, or accelOffsets. Each element in the list is the general offset for an acceleration axis. The length of 9 was chosen in order to match the 9 sensor axes on the module, in the event that calibration offsets were ever needed for the gyroscope and magnetometer (only 3 elements are actually populated).

SerialComm returns these offsets in the form of an int[][] named MPUMinMax, reading data directly from the module instead of from the testParameters CSVP file. This is the precursor to mpuOffsets and is structured as follows:

  • (0) Acceleration X
    • (0) Minimum Offset
    • (1) Maximum Offset
  • (1) Acceleration Y
    • (0) Minimum Offset
    • (1) Maximum Offset
  • (2) Acceleration Z
    • (0) Minimum Offset
    • (1) Maximum Offset

Data Conversion

This section describes various formats used in DataOrganizer, GenericTest, and AxisDataSeries for converting raw data samples to physical quantities.

Data Samples

Once data is read from the module via SerialComm converting byte format into 16-bit unsigned integer format, the raw data samples are stored in a List<List<Double>> named dataSamples. Each inner list represents a single sensor axis (accelerometer, gyroscope, and magnetometer along with either X, Y, or Z). The order of these lists is shown below:

  1. Time
  2. Acceleration X
  3. Acceleration Y
  4. Acceleration Z
  5. Gyroscope (Angular Velocity) X
  6. Gyroscope (Angular Velocity) Y
  7. Gyroscope (Angular Velocity) Z
  8. Magnetometer X
  9. Magnetometer Y
  10. Magnetometer Z

Axis Data Series

The BioForce Graph further processes dataSamples into individual AxisDataSeries objects. Each AxisDataSeries represents a single axis's data (eg. Acceleration X or Angular Velocity Y). The key difference from dataSamples is that AxisDataSeries supports "virtual" axes -- generating integrated/differentiated data sets, such as velocity or angular acceleration, from native sensor data sets. In addition, AxisDataSeries encapsulates all methods related to data conversion, calculation, and manipulations in a single class. This improves readability and changes without affecting other portions of the codebase.

GenericTest, which represents all test data associated with a single module, contains the list of axes in its field AxisDataSeries[] axes, the format of which is described below:

  1. Acceleration X
  2. Acceleration Y
  3. Acceleration Z
  4. Acceleration Magnitude
  5. Velocity X
  6. Velocity Y
  7. Velocity Z
  8. Velocity Magnitude
  9. Displacement X
  10. Displacement Y
  11. Displacement Z
  12. Displacement Magnitude
  13. Angular Acceleration X
  14. Angular Acceleration Y
  15. Angular Acceleration Z
  16. Angular Acceleration Magnitude
  17. Angular Velocity X
  18. Angular Velocity Y
  19. Angular Velocity Z
  20. Angular Velocity Magnitude
  21. Angular Displacement X
  22. Angular Displacement Y
  23. Angular Displacement Z
  24. Angular Displacement Magnitude
  25. Magnetometer X
  26. Magnetometer Y
  27. Magnetometer Z
  28. Magnetometer Magnitude

Within an AxisDataSeries, raw data is processed in the following steps:

  1. Sign data samples,
  2. apply sensor sensitivity,
  3. normalize the data sets,
  4. and smooth the graphs.

Signing Data

As mentioned previously, data samples are originally recorded as 16-bit unsigned integers (meaning a number between 0 and 65535). However, since all physical quantities have magnitude, the Dashboard must "sign" the data, converting the all-positive integer range (0-65535) to both positive and negative integers.

To do this, the top half of the range (32768-65535) is mapped to negative numbers, while the bottom half (0-32767) maps to positive numbers. In doing so, the number of possible values in the range would remain the same (a total of 65536 values), but it would simply be shifted to encompass a "signed" range (-32767,32767).

You may notice that the number of values in the range above, (-32767,32767), is only 65535, instead of 65536. This is because the signed number 0 is actually mapped to two unsigned numbers, 0 and 65535. This is intentional as you'll see later.

Specifically, the "signing" algorithm to convert unsigned samples to signed samples boils down to the following:

if sample > 32767, subtract 65535;
else, leave sample unchanged

This obeys two's complement representation, resulting in both the minimum and maximum unsigned values (0 and 65535) being mapped to the signed value 0.

Example: the raw sample 42439 becomes the signed sample -23096.

Sensitivity and Resolution

Once data samples are signed, they must be translated into their physical quantities using the sensor's given sensitivity. This can be thought of as how "wide" the range of data can be. For example, a sensitivity of 16G would allow the module to measure data samples anywhere from -16Gs of motion to +16Gs of motion, while a sensitivity of 8G would measure data between -8Gs and +8Gs.

However, since raw data samples are fixed to only 65535 unique values, changing a sensor's sensitivity directly affects its resolution. This can be thought of as how "precise" its measurements are.

For example, with a sensitivity of 16G, each of the 65535 possible values would be more "spread out" to cover the entire range (to be specific, each sample would measure in increments of 16G / 32768 = 4.88*10^-4 Gs). However, with a sensitivity of 8G, the 65535 values would be much closer together since the range is smaller. This allows data samples to have finer increments of 8G / 32768 = 2.44*10^-4 Gs.

The figure below illustrates the core concept described earlier:

Sensitivity/Resolution Graphic

With all this, we now have all the information needed to convert our raw data. To find where a given signed data sample "falls" on the scale of 65535 possible values (see above), we can divide by the maximum magnitude of the range, 32768. Using the same logic, we then multiply by the sensitivity, which represents the maximum magnitude of the range recorded by the sensor. In other words, these two operations convert any sample from the "raw" range of (-32767,32767) to the "physical" range of (-sensitivity,+sensitivity).

Therefore, the final equation to convert signed samples to physical quantities is:

physical quantity = (signed data sample) * (sensitivity) / 32768

Example: the signed sample -23096 recorded at 16G sensitivity is -23096 * 16 / 32768 = -11.27G, or -110.56 m/s^2.