Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LOC - Implement Navigator #47

Open
wants to merge 50 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
79fc2f4
Initial commit
Sean-Leishman Oct 15, 2024
8299f39
Fixup types in use of entry preprocess function
Sean-Leishman Oct 15, 2024
213fce5
Update preprocessor main entry functions
Sean-Leishman Oct 23, 2024
d5be831
Clone getQuartiles implementation
Sean-Leishman Oct 23, 2024
e53f612
Ensure all reliable accelerometers are kept in range
Sean-Leishman Oct 24, 2024
a09ae8f
Add additional tests for quartile calculation
Sean-Leishman Oct 28, 2024
9fee8f8
Add handle_outlier tests
Sean-Leishman Oct 31, 2024
36b2976
Use localisation/src/types.rs for constants
Sean-Leishman Nov 1, 2024
62a36b9
Run clippy
Sean-Leishman Nov 1, 2024
d1177d0
Clean-up linter errors
Sean-Leishman Nov 1, 2024
b753953
Fix testing linter warnings
Sean-Leishman Nov 1, 2024
aa4b41c
QoL changes
Sean-Leishman Nov 11, 2024
58bcf9e
Remove extra impl
Sean-Leishman Nov 11, 2024
3cf7fd3
Default impl for clippy
Sean-Leishman Nov 11, 2024
34c7207
Merge remote-tracking branch 'origin/main' into hype-43-loc-accelerom…
Sean-Leishman Nov 11, 2024
6e6a502
initial commit
misha7b Nov 11, 2024
3a9585e
getters & setters
misha7b Nov 11, 2024
b352a14
new name!
misha7b Nov 11, 2024
847c2d9
Add comments and tests
Sean-Leishman Nov 14, 2024
5bc0704
clippy
misha7b Nov 16, 2024
894169c
small fixes to types
misha7b Nov 16, 2024
0ad0161
fmt!
misha7b Nov 16, 2024
98624df
Merge branch 'main' into hype-43-loc-accelerometer-preprocessor
misha7b Nov 16, 2024
266877a
Merge branch 'main' into transfer_navigator
misha7b Nov 16, 2024
c6ad181
Merge branch 'hype-43-loc-accelerometer-preprocessor' into transfer_n…
misha7b Nov 16, 2024
429ec17
small bug fixex
misha7b Nov 16, 2024
70bf0a9
Merge branch 'main' into transfer_navigator
misha7b Nov 16, 2024
61e6cfb
deleted redundant file
misha7b Nov 16, 2024
222c2a1
smallfix
misha7b Nov 16, 2024
24a43c2
initialised kalman filter
misha7b Nov 16, 2024
2392c54
structure changes
misha7b Nov 16, 2024
9199d1f
File name changes
misha7b Nov 16, 2024
8a97f96
working
misha7b Nov 16, 2024
666ca56
spelling
misha7b Nov 16, 2024
c635a37
preprocessor
misha7b Nov 16, 2024
cecf033
format|
misha7b Nov 16, 2024
eeda22a
Fixed preprocess_keyence: changed input vector from bool to int
misha7b Nov 18, 2024
d815e2a
small fixes
misha7b Nov 18, 2024
34105ec
Finished... I think?
misha7b Nov 21, 2024
55c0c26
CLIPPYgit add .! and also half-assed acceleration calculation:
misha7b Nov 21, 2024
39fc6a5
this commit will fail
misha7b Nov 21, 2024
545b4dc
i command you to build
misha7b Nov 21, 2024
6f8d85e
CLIPPY!
misha7b Nov 21, 2024
eaa32c3
simple silly test
misha7b Nov 21, 2024
7bfab5d
removed comment
misha7b Jan 13, 2025
73227cf
fmt
misha7b Jan 13, 2025
3aa1a45
comment
misha7b Jan 13, 2025
6427166
Merge branch 'main' into transfer_navigator
misha7b Jan 13, 2025
f38152e
comment
misha7b Jan 13, 2025
2eb9bd7
Changed from 2 to 1 Optical Flow Sensor
misha7b Jan 20, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/localisation/src/control.rs
Original file line number Diff line number Diff line change
@@ -1 +1 @@
pub mod navigator;
pub mod localizer;
186 changes: 186 additions & 0 deletions lib/localisation/src/control/localizer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
use crate::{
filtering::kalman_filter::KalmanFilter,
preprocessing::{
accelerometer::AccelerometerPreprocessor,
keyence::{KeyenceAgrees, SensorChecks},
optical::process_optical_data,
},
types::{RawAccelerometerData, K_NUM_ACCELEROMETERS, K_NUM_AXIS},
};

use heapless::Vec;

use libm::pow;
use nalgebra::{Matrix2, Vector1, Vector2};

const DELTA_T: f64 = 0.01;
const STRIPE_WIDTH: f64 = 1.0;
Comment on lines +16 to +17
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be good to add a comment with the units for these


pub struct Localizer {
displacement: f64,
velocity: f64,
previous_velocity: f64,
acceleration: f64,
kalman_filter: KalmanFilter,
keyence_checker: KeyenceAgrees,
keyence_val: f64,
optical_val: f64,
accelerometer_val: f64,
}

impl Localizer {
pub fn new() -> Localizer {
let initial_state = Vector2::new(0.0, 0.0);
let initial_covariance = Matrix2::new(1.0, 0.0, 0.0, 1.0);
let transition_matrix = Matrix2::new(1.0, DELTA_T, 0.0, 1.0);
let control_matrix = Vector2::new(0.5 * DELTA_T * DELTA_T, DELTA_T);
let observation_matrix = Matrix2::new(1.0, 0.0, 0.0, DELTA_T);

// Assuming frequency of 6400hz for IMU at 120 mu g / sqrt(Hz)
// standard deviation = 120 * sqrt(6400) = 9600 mu g = 0.0096 g
// = 0.0096 * 9.81 = 0.094176 m/s^2
// variance = 0.094176^2 = 0.0089 m/s^2
Comment on lines +39 to +42
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

really good comments here


let process_noise: Matrix2<f64> = Matrix2::new(
0.25 * pow(DELTA_T, 4.0),
0.5 * pow(DELTA_T, 3.0),
0.5 * pow(DELTA_T, 3.0),
pow(DELTA_T, 2.0) * 0.0089,
);

// We assume the stripe counter is accurate
// Optical flow expects standard deviation of 0.01% of the measured value
// Assuming top speed 10m/s,
// standard deviation = 0.01 * 10 = 0.1 m/s
// variance = 0.1^2 = 0.01 m/s^2

let measurement_noise: Matrix2<f64> = Matrix2::new(0.01, 0.0, 0.0, 0.0);

let kalman_filter = KalmanFilter::new(
initial_state,
initial_covariance,
transition_matrix,
control_matrix,
observation_matrix,
process_noise,
measurement_noise,
);

Localizer {
displacement: 0.0,
velocity: 0.0,
previous_velocity: 0.0,
acceleration: 0.0,
kalman_filter,
keyence_checker: KeyenceAgrees::new(),
keyence_val: 0.0,
optical_val: 0.0,
accelerometer_val: 0.0,
}
}
}

impl Default for Localizer {
fn default() -> Self {
Self::new()
}
}

impl Localizer {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some Rust doc /// comments for Localizer and the preprocessor and iteration functions would be great

pub fn preprocessor(
&mut self,
optical_data: Vec<f64, 2>,
keyence_data: Vec<u32, 2>,
accelerometer_data: RawAccelerometerData<K_NUM_ACCELEROMETERS, K_NUM_AXIS>,
) {
let processed_optical_data = process_optical_data(optical_data.clone());
self.optical_val = processed_optical_data as f64;

let keyence_status = self
.keyence_checker
.check_keyence_agrees(keyence_data.clone());

if keyence_status == SensorChecks::Unacceptable {
//TODOLater: Change state
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rather than handling the logic for changing state in here, the whole preprocessor function could return Result<(), PreprocessorError> or similar in this case so that the caller can deal with it instead

return;
} else {
//TODOLater: Check unit of keyence data
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what unit is this referring to?

self.keyence_val = keyence_data[0] as f64;
}

let mut accelerometer_preprocessor = AccelerometerPreprocessor::new();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be better to just have one instance of AccelerometerPreprocessor like with Keyence so it's not being constantly created

let processed_accelerometer_data =
accelerometer_preprocessor.process_data(accelerometer_data);
if processed_accelerometer_data.is_none() {
// TODOLater: Change state
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

likewise here

return;
}

let processed_accelerometer_data = processed_accelerometer_data.unwrap();
self.accelerometer_val = 0.0;
for i in 0..K_NUM_ACCELEROMETERS {
for _ in 0..K_NUM_AXIS {
self.accelerometer_val += processed_accelerometer_data[i] as f64;
}
}
self.accelerometer_val /= (K_NUM_ACCELEROMETERS * K_NUM_AXIS) as f64;
}

pub fn iteration(
&mut self,
optical_data: Vec<f64, 2>,
keyence_data: Vec<u32, 2>,
accelerometer_data: RawAccelerometerData<K_NUM_ACCELEROMETERS, K_NUM_AXIS>,
) {
self.preprocessor(
optical_data.clone(),
keyence_data.clone(),
accelerometer_data.clone(),
);
Comment on lines +135 to +139
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if you change the return type then iteration will also return a Result type, so just pass the error up


let control_input = Vector1::new(self.accelerometer_val);

self.kalman_filter.predict(&control_input);

//TODOLater: Check unit of keyence data
let measurement = Vector2::new(self.keyence_val * STRIPE_WIDTH, self.optical_val);

self.kalman_filter.update(&measurement);

let state = self.kalman_filter.get_state();

self.displacement = state[0];
self.velocity = state[1];
self.acceleration = (self.velocity - self.previous_velocity) / DELTA_T; //TODOLater: is this accurate enough?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How are you going to determine whether this is accurate enough? Is that something that needs physical testing?

self.previous_velocity = self.velocity;
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_localizer_with_zeros() {
let mut localizer = Localizer::default();

let optical_data: Vec<f64, 2> = Vec::from_slice(&[0.0, 0.0]).unwrap();

let raw_keyence_data: Vec<u32, 2> = Vec::from_slice(&[0, 0]).unwrap();

let raw_accelerometer_data: RawAccelerometerData<K_NUM_ACCELEROMETERS, K_NUM_AXIS> =
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't spot this earlier, but we don't need K_ prefix for K_NUM_ACCELEROMETERS or K_NUM_AXIS

RawAccelerometerData::from_slice(&[
Vec::from_slice(&[0.0, 0.0, 0.0]).unwrap(),
Vec::from_slice(&[0.0, 0.0, 0.0]).unwrap(),
Vec::from_slice(&[0.0, 0.0, 0.0]).unwrap(),
Vec::from_slice(&[0.0, 0.0, 0.0]).unwrap(),
])
.unwrap();

localizer.iteration(optical_data, raw_keyence_data, raw_accelerometer_data);

assert_eq!(localizer.displacement, 0.0);
assert_eq!(localizer.velocity, 0.0);
assert_eq!(localizer.acceleration, 0.0);
}
}
1 change: 0 additions & 1 deletion lib/localisation/src/control/navigator.rs

This file was deleted.

7 changes: 4 additions & 3 deletions lib/localisation/src/preprocessing/accelerometer.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::types::{AccelerometerData, RawAccelerometerData, SensorChecks};

use super::super::types::{K_NUM_ACCELEROMETERS, K_NUM_ALLOWED_ACCELEROMETER_OUTLIERS, K_NUM_AXIS};
use crate::types::{
AccelerometerData, RawAccelerometerData, SensorChecks, K_NUM_ACCELEROMETERS,
K_NUM_ALLOWED_ACCELEROMETER_OUTLIERS, K_NUM_AXIS,
};
use heapless::Vec;

/// Stores the quartiles of the data and the bounds for outliers
Expand Down
24 changes: 12 additions & 12 deletions lib/localisation/src/preprocessing/keyence.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use heapless::Vec;
#[derive(PartialEq, Debug)]
pub enum SensorChecks {
Acceptable,
Unnaceptable,
Unacceptable,
}

/// Checks if the two Keyence sensors are in agreement.
Expand All @@ -25,9 +25,9 @@ impl KeyenceAgrees {
}
}

pub fn check_keyence_agrees(&mut self, keyence_data: Vec<bool, 2>) -> SensorChecks {
pub fn check_keyence_agrees(&mut self, keyence_data: Vec<u32, 2>) -> SensorChecks {
if keyence_data[0] != keyence_data[1] && !self.previous_keyence_agreement {
return SensorChecks::Unnaceptable;
return SensorChecks::Unacceptable;
} else {
self.previous_keyence_agreement = keyence_data[0] == keyence_data[1];
}
Expand All @@ -42,7 +42,7 @@ mod tests {

#[test]
fn test_acceptable_success() {
let keyence_data: Vec<bool, 2> = Vec::from_slice(&[true, true]).unwrap();
let keyence_data: Vec<u32, 2> = Vec::from_slice(&[0, 1]).unwrap();
let mut keyence_agrees = KeyenceAgrees::new();
let desired_outcome = SensorChecks::Acceptable;
let result = keyence_agrees.check_keyence_agrees(keyence_data);
Expand All @@ -51,7 +51,7 @@ mod tests {

#[test]
fn test_acceptable_false_success() {
let keyence_data: Vec<bool, 2> = Vec::from_slice(&[true, false]).unwrap();
let keyence_data: Vec<u32, 2> = Vec::from_slice(&[0, 1]).unwrap();
let mut keyence_agrees = KeyenceAgrees::new();
let desired_outcome = SensorChecks::Acceptable;
let result = keyence_agrees.check_keyence_agrees(keyence_data);
Expand All @@ -60,8 +60,8 @@ mod tests {

#[test]
fn test_acceptable_second_false_success() {
let first_keyence_data: Vec<bool, 2> = Vec::from_slice(&[true, true]).unwrap();
let second_keyence_data: Vec<bool, 2> = Vec::from_slice(&[true, false]).unwrap();
let first_keyence_data: Vec<u32, 2> = Vec::from_slice(&[1, 1]).unwrap();
let second_keyence_data: Vec<u32, 2> = Vec::from_slice(&[1, 1]).unwrap();
let mut keyence_agrees = KeyenceAgrees::new();
let desired_outcome = SensorChecks::Acceptable;
let initial_try = keyence_agrees.check_keyence_agrees(first_keyence_data);
Expand All @@ -72,8 +72,8 @@ mod tests {

#[test]
fn test_acceptable_prev_false_success() {
let first_keyence_data: Vec<bool, 2> = Vec::from_slice(&[true, false]).unwrap();
let second_keyence_data: Vec<bool, 2> = Vec::from_slice(&[true, true]).unwrap();
let first_keyence_data: Vec<u32, 2> = Vec::from_slice(&[1, 2]).unwrap();
let second_keyence_data: Vec<u32, 2> = Vec::from_slice(&[1, 1]).unwrap();
let mut keyence_agrees = KeyenceAgrees::new();
let desired_outcome = SensorChecks::Acceptable;
let initial_try = keyence_agrees.check_keyence_agrees(first_keyence_data);
Expand All @@ -84,11 +84,11 @@ mod tests {

#[test]
fn test_unnacceptable_prev_false_success() {
let first_keyence_data: Vec<bool, 2> = Vec::from_slice(&[true, false]).unwrap();
let second_keyence_data: Vec<bool, 2> = Vec::from_slice(&[true, false]).unwrap();
let first_keyence_data: Vec<u32, 2> = Vec::from_slice(&[1, 2]).unwrap();
let second_keyence_data: Vec<u32, 2> = Vec::from_slice(&[2, 3]).unwrap();
let mut keyence_agrees = KeyenceAgrees::new();
let first_outcome = SensorChecks::Acceptable;
let second_outcome = SensorChecks::Unnaceptable;
let second_outcome = SensorChecks::Unacceptable;
let initial_try = keyence_agrees.check_keyence_agrees(first_keyence_data);
let result = keyence_agrees.check_keyence_agrees(second_keyence_data);
assert_eq!(initial_try, first_outcome);
Expand Down
1 change: 0 additions & 1 deletion lib/localisation/src/preprocessing/lib.rs

This file was deleted.

47 changes: 15 additions & 32 deletions lib/localisation/src/preprocessing/optical.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,15 @@ use heapless::Vec;
use libm::sqrtf;

/// Processes the raw optical data to get the magnitude and added to the optical data for each sensor
pub fn process_data(raw_optical_data: Vec<Vec<f64, 2>, 2>) -> Vec<f32, 2> {
let mut optical_data: Vec<f32, 2> = Vec::from_slice(&[0.0, 0.0]).unwrap();
pub fn process_optical_data(raw_optical_data: Vec<f64, 2>) -> f32 {
let mut magnitude: f32 = 0.0;
Comment on lines +5 to +6
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to keep this generic to any number of optical flow sensors? Could take the number as a constant NUM_OPTICAL_FLOW_SENSORS like with accelerometers, and just have a Vec of length 1 if there's only one?


for i in 0..2 {
let mut magnitude: f32 = 0.0;

for data in raw_optical_data[i].clone() {
let data: f32 = data as f32;
magnitude += data * data;
}
optical_data[i] = sqrtf(magnitude);
for data in raw_optical_data {
let data: f32 = data as f32;
magnitude += data * data;
}

optical_data
sqrtf(magnitude)
}

#[cfg(test)]
Expand All @@ -24,37 +19,25 @@ mod tests {

#[test]
fn test_correct_positive() {
let raw_optical_data: Vec<Vec<f64, 2>, 2> = Vec::from_slice(&[
Vec::from_slice(&[1.0, 1.0]).unwrap(),
Vec::from_slice(&[3.0, 4.0]).unwrap(),
])
.unwrap();
let desired_outcome: Vec<f32, 2> = Vec::from_slice(&[sqrtf(2.0), 5.0]).unwrap();
let result = process_data(raw_optical_data);
let raw_optical_data: Vec<f64, 2> = Vec::from_slice(&[1.0, 1.0]).unwrap();
let desired_outcome: f32 = sqrtf(2.0);
let result = process_optical_data(raw_optical_data);
assert_eq!(result, desired_outcome);
}

#[test]
fn test_correct_negative() {
let raw_optical_data: Vec<Vec<f64, 2>, 2> = Vec::from_slice(&[
Vec::from_slice(&[-4.0, -6.0]).unwrap(),
Vec::from_slice(&[-3.0, -1.0]).unwrap(),
])
.unwrap();
let desired_outcome: Vec<f32, 2> = Vec::from_slice(&[7.2111025, 3.1622777]).unwrap();
let result = process_data(raw_optical_data);
let raw_optical_data: Vec<f64, 2> = Vec::from_slice(&[-4.0, -6.0]).unwrap();
let desired_outcome: f32 = sqrtf(52.0);
let result = process_optical_data(raw_optical_data);
assert_eq!(result, desired_outcome);
}

#[test]
fn test_correct_zero() {
let raw_optical_data: Vec<Vec<f64, 2>, 2> = Vec::from_slice(&[
Vec::from_slice(&[0.0, 0.0]).unwrap(),
Vec::from_slice(&[0.0, 0.0]).unwrap(),
])
.unwrap();
let desired_outcome: Vec<f32, 2> = Vec::from_slice(&[0.0, 0.0]).unwrap();
let result = process_data(raw_optical_data);
let raw_optical_data: Vec<f64, 2> = Vec::from_slice(&[0.0, 0.0]).unwrap();
let desired_outcome: f32 = 0.0;
let result = process_optical_data(raw_optical_data);
assert_eq!(result, desired_outcome);
}
}
Loading