1
Fork 0

re-embedded auto in interface

crashes seem to occur when mutating state from across the dylib boundary

not worth troubleshooting when I don't need hot-reloading anymore
This commit is contained in:
Andy Killorin 2025-03-28 00:08:13 -04:00
parent 9a68f43e5d
commit 17f484acd5
Signed by: ank
GPG key ID: 23F9463ECB67FE8C
5 changed files with 164 additions and 20 deletions

View file

@ -1,4 +1,4 @@
use std::{future::Future, ops::{Deref, Range}, pin::Pin, sync::{atomic::{AtomicBool, Ordering}, Arc}}; use std::{future::Future, ops::{Deref, Range}, pin::Pin, sync::{atomic::{AtomicBool, Ordering}, Arc}, thread::sleep, time::Duration};
use common::{CamState, ControlPacket, SensorData, TelemetryPacket}; use common::{CamState, ControlPacket, SensorData, TelemetryPacket};
use anyhow::{anyhow, Ok, Result}; use anyhow::{anyhow, Ok, Result};
@ -53,7 +53,15 @@ impl AutoInterface {
pub async fn sensor_update(&mut self) -> SensorData { pub async fn sensor_update(&mut self) -> SensorData {
self.data_receiver.changed().await.unwrap(); self.data_receiver.changed().await.unwrap();
self.data_receiver.borrow().sensors.clone() self.data_receiver.borrow().sensors.clone()
}
pub fn sensor_update_blocking(&mut self) -> SensorData {
loop {
if self.data_receiver.has_changed().unwrap() {
break;
}
sleep(Duration::from_millis(10)); // TODO: exponential or condvars
}
self.data_receiver.borrow_and_update().sensors.clone()
} }
/// disable auto /// disable auto
pub fn disable(&mut self) { pub fn disable(&mut self) {
@ -140,12 +148,12 @@ impl Auto {
} }
/// entrypoint /// entrypoint
pub async fn run(&self, interface: AutoInterface) -> Result<JoinHandle<()>> { pub fn run(&self, interface: AutoInterface) -> Result<()> {
let func: Symbol<unsafe extern "C" fn(AutoInterface) -> Pin<Box<dyn Future<Output = ()> + Send>>> = unsafe {self.library.get(b"entry")?}; let func: Symbol<unsafe extern "C" fn(AutoInterface) -> ()> = unsafe {self.library.get(b"entry")?};
let fut = unsafe { func(interface.clone()) }; unsafe { func(interface.clone()) };
Ok(tokio::spawn(fut)) Ok(())
} }
pub fn configs(&self) -> &'static [Configurable] { pub fn configs(&self) -> &'static [Configurable] {

146
interface/src/auto_impl.rs Normal file
View file

@ -0,0 +1,146 @@
use std::{collections::VecDeque, future::Future, ops::Sub, pin::Pin};
use common::CamState;
use crate::auto::{AutoInterface, Configurable};
#[unsafe(no_mangle)]
pub fn entry(interface: AutoInterface) -> Pin<Box<dyn Future<Output = ()> + Send>> {
Box::pin(auto(interface))
}
#[unsafe(no_mangle)]
pub static NAME: &'static str = "scanseek v1";
const AUTO_GAP: Configurable = Configurable::new("auto minimum gap").range(0. .. 300.).default(140.)
.description("distance (mm) distance measurements must instantaneously drop to indicate a detection. This should line up with the size of the smallest robot you compete against");
const AUTO_SELF_OCCLUSION: Configurable = Configurable::new("auto self occlusion").range(0. .. 200.).default(143.)
.description("distance (mm) below which measurements are considered noise in the scan phase");
const AUTO_CONVERGENCE_POINT: Configurable = Configurable::new("auto convergence").range(100. .. 300.).default(190.)
.description("distance (mm) where the tof beams intersect");
const AUTO_SEEK_RANGE: Configurable = Configurable::new("auto seek range").range(200. .. 8000.).default(500.);
#[unsafe(no_mangle)]
pub static CONFIGS: &[Configurable] = &[
AUTO_GAP,
AUTO_SELF_OCCLUSION,
AUTO_CONVERGENCE_POINT,
AUTO_SEEK_RANGE,
];
pub async fn auto(mut interface: AutoInterface) {
let mut tof_l = Stats::new();
let mut tof_r = Stats::new();
loop {
let data = interface.sensor_update().await;
println!("auto got on data");
let cam = interface.cam_state();
let CamState::Charged = cam else {
continue;
};
let Some(latest_tof_l) = data.tof_l else {
continue;
};
tof_l.update(latest_tof_l as i16);
let Some(latest_tof_r) = data.tof_r else {
continue;
};
tof_r.update(latest_tof_r as i16);
let auto_gap = interface.conf(&AUTO_GAP);
let auto_self_occlusion = interface.conf(&AUTO_SELF_OCCLUSION);
let detection = |latest: u16, delta: i16| {
delta < auto_gap as i16 && latest > auto_self_occlusion as u16
};
if detection(latest_tof_l, tof_l.delta()) || detection(latest_tof_l, tof_l.delta()) {
if let Ok(()) = interface.enable() {
println!("found, now seek");
seek(interface.clone(), &mut tof_l, &mut tof_r).await;
} else {
println!("requested enable")
}
}
}
}
async fn seek(mut interface: AutoInterface, tof_l: &mut Stats<i16>, tof_r: &mut Stats<i16>) {
let crossover = interface.conf(&AUTO_CONVERGENCE_POINT) as i16;
let range = interface.conf(&AUTO_SEEK_RANGE) as i16;
loop {
let data = interface.sensor_data();
data.tof_l.map(|d| tof_l.update(d as i16));
data.tof_r.map(|d| tof_r.update(d as i16));
let left_near = tof_l.latest() < crossover && tof_l.delta() < 70;
let right_near = tof_r.latest() < crossover && tof_r.delta() < 70;
let left_far = tof_l.latest() > crossover && tof_l.latest() < range && tof_l.delta() < 70;
let right_far = tof_r.latest() > crossover && tof_r.latest() < range && tof_r.delta() < 70;
let near = left_near || right_near;
let far = !near && (left_far || right_far);
let mut twist = 0.0;
if near {
if tof_l.max() > tof_r.max() {
twist = 1.0; // right
} else {
twist = -1.0; // left
}
} else if far {
if tof_l.max() > tof_r.max() {
twist = -1.0; // left
} else {
twist = 1.0; // right
}
}
let _ = interface.run_command(common::ControlPacket::Twist(1.0, twist));
interface.sensor_update_blocking();
}
}
struct Stats<T> {
table: VecDeque<T>
}
impl<T: Copy> Stats<T> {
pub fn new() -> Self {
Self { table: VecDeque::new() }
}
const MAX_ELEMENTS: usize = 3;
pub fn update(&mut self, elem: T) {
self.table.push_front(elem);
if self.table.len() > Self::MAX_ELEMENTS {
self.table.pop_back();
}
}
/// panics if no values have been written
pub fn latest(&self) -> T {
self.table.front().unwrap().to_owned()
}
}
impl<T: Ord + Sub<Output = T> + Copy + From<u8>> Stats<T> {
pub fn delta(&self) -> T {
*self.table.get(0).unwrap_or(&T::from(0)) - *self.table.get(1).unwrap_or(&T::from(0))
}
pub fn max(&self) -> T {
*self.table.iter().max().unwrap_or(&T::from(0))
}
pub fn min(&self) -> T {
*self.table.iter().max().unwrap_or(&T::from(0))
}
}

View file

@ -6,7 +6,7 @@ use image::ImageFormat;
use tokio::{runtime::Runtime, sync::{mpsc, watch::{self, Receiver}}}; use tokio::{runtime::Runtime, sync::{mpsc, watch::{self, Receiver}}};
use egui_toast::{Toast, Toasts}; use egui_toast::{Toast, Toasts};
use crate::{auto::Configurable, storage::StorageManager, POWER_THRESHOLD}; use crate::{auto::Configurable, auto_impl::CONFIGS, storage::StorageManager, POWER_THRESHOLD};
pub const GUI: OnceCell<Context> = OnceCell::new(); pub const GUI: OnceCell<Context> = OnceCell::new();
@ -82,17 +82,6 @@ impl GUI {
} }
} }
// dupe from auto crate for testing
const AUTO_GAP: Configurable = Configurable::new("auto minimum gap").range(0. .. 300.).default(140.)
.description("distance (mm) distance measurements must instantaneously drop to indicate a detection. This should line up with the size of the smallest robot you compete against");
const AUTO_SELF_OCCLUSION: Configurable = Configurable::new("auto self occlusion").range(0. .. 200.).default(143.)
.description("distance (mm) below which measurements are considered noise in the scan phase");
pub static CONFIGS: &[Configurable] = &[
AUTO_GAP,
AUTO_SELF_OCCLUSION,
];
impl eframe::App for GUI { impl eframe::App for GUI {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
let _ = GUI.set(ctx.clone()); let _ = GUI.set(ctx.clone());

View file

@ -6,6 +6,7 @@ use gui::DEFAULT_VOLUME_THRESHOLD;
pub mod gui; pub mod gui;
pub mod combatlog; pub mod combatlog;
pub mod auto; pub mod auto;
pub mod auto_impl;
pub mod storage; pub mod storage;
pub const POWER_THRESHOLD: AtomicF32 = AtomicF32::new(DEFAULT_VOLUME_THRESHOLD); pub const POWER_THRESHOLD: AtomicF32 = AtomicF32::new(DEFAULT_VOLUME_THRESHOLD);

View file

@ -3,7 +3,7 @@ use std::{fmt::format, ops::ControlFlow, path::Path, result, sync::{atomic::{Ato
use anyhow::{Context, Ok, Result}; use anyhow::{Context, Ok, Result};
use atomic_float::AtomicF32; use atomic_float::AtomicF32;
use directories::ProjectDirs; use directories::ProjectDirs;
use interface::{auto::{get_confs, Auto, AutoInterface}, combatlog::{combat_logger, CombatData}, gui::CONFIGS, storage::StorageManager, POWER_THRESHOLD}; use interface::{auto::{get_confs, Auto, AutoInterface}, auto_impl, combatlog::{combat_logger, CombatData}, gui::CONFIGS, storage::StorageManager, POWER_THRESHOLD};
use common::{ControlPacket, TelemetryPacket}; use common::{ControlPacket, TelemetryPacket};
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
use egui_toast::{Toast, ToastKind}; use egui_toast::{Toast, ToastKind};
@ -126,7 +126,7 @@ fn main() -> Result<()> {
let spawner = executor.handle().clone(); let spawner = executor.handle().clone();
thread::spawn(move || { thread::spawn(move || {
spawner.block_on(async { spawner.block_on(async {
let _stoppie = auto.run(interface_).await.unwrap(); let _stoppie = auto_impl::auto(interface_).await;
println!("auto ended"); println!("auto ended");
}); });
}); });