added combat logger
A refactor might be beneficial here, now data is being sent to multiple streams which feels a tad off
This commit is contained in:
parent
e352c5b048
commit
95c5b50dd3
6 changed files with 297 additions and 11 deletions
|
@ -39,13 +39,13 @@ pub struct SensorData {
|
|||
pub accel: Option<Vector3<f32>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct TelemetryPacket {
|
||||
pub sensors: SensorData,
|
||||
pub cam_state: CamState,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub enum CamState {
|
||||
Firing,
|
||||
Charged,
|
||||
|
|
73
interface/Cargo.lock
generated
73
interface/Cargo.lock
generated
|
@ -201,6 +201,12 @@ version = "0.2.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04"
|
||||
|
||||
[[package]]
|
||||
name = "android-tzdata"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
|
||||
|
||||
[[package]]
|
||||
name = "android_system_properties"
|
||||
version = "0.1.5"
|
||||
|
@ -737,6 +743,20 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c"
|
||||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"wasm-bindgen",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clang-sys"
|
||||
version = "1.8.1"
|
||||
|
@ -1183,6 +1203,18 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "embedded-io"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced"
|
||||
|
||||
[[package]]
|
||||
name = "embedded-io"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d"
|
||||
|
||||
[[package]]
|
||||
name = "endi"
|
||||
version = "1.1.0"
|
||||
|
@ -1714,6 +1746,29 @@ dependencies = [
|
|||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.61"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"core-foundation-sys",
|
||||
"iana-time-zone-haiku",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
"windows-core 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone-haiku"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_collections"
|
||||
version = "1.5.0"
|
||||
|
@ -1890,6 +1945,7 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"anyhow",
|
||||
"atomic_float",
|
||||
"chrono",
|
||||
"common",
|
||||
"cpal",
|
||||
"eframe",
|
||||
|
@ -2738,6 +2794,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "170a2601f67cc9dba8edd8c4870b15f71a6a2dc196daec8c83f72b59dff628a8"
|
||||
dependencies = [
|
||||
"cobs",
|
||||
"embedded-io 0.4.0",
|
||||
"embedded-io 0.6.1",
|
||||
"heapless",
|
||||
"serde",
|
||||
]
|
||||
|
@ -3938,6 +3996,15 @@ dependencies = [
|
|||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.54.0"
|
||||
|
@ -3983,6 +4050,12 @@ dependencies = [
|
|||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6dccfd733ce2b1753b03b6d3c65edf020262ea35e20ccdf3e288043e6dd620e3"
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.1.2"
|
||||
|
|
|
@ -7,9 +7,9 @@ edition = "2021"
|
|||
anyhow = "1.0.95"
|
||||
cpal = "0.15.3"
|
||||
pitch-detection = "0.3"
|
||||
postcard = "1.1.1"
|
||||
postcard = { version = "1.1.1", features = ["alloc", "use-std"] }
|
||||
rust-music-theory = {git = "https://github.com/the-drunk-coder/rust-music-theory", rev = "a062d65"}
|
||||
serde = "1.0.217"
|
||||
serde = { version = "1.0.217", features = ["derive"] }
|
||||
tokio = { version = "1.43.0", features = ["full"] }
|
||||
common = {path = "../common"}
|
||||
heapless = "0.7.0"
|
||||
|
@ -19,3 +19,4 @@ egui_extras = { version = "0.30", features = ["default", "image"] }
|
|||
egui-toast = "0.16.0"
|
||||
home = "0.5.11"
|
||||
atomic_float = "1.1.0"
|
||||
chrono = "0.4.40"
|
||||
|
|
42
interface/src/combatlog.rs
Normal file
42
interface/src/combatlog.rs
Normal file
|
@ -0,0 +1,42 @@
|
|||
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||
|
||||
use anyhow::Ok;
|
||||
use common::{ControlPacket, TelemetryPacket};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::{fs::File, io::AsyncWriteExt, sync::mpsc::Receiver, time::Instant};
|
||||
|
||||
use crate::storage_dir::storage;
|
||||
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub enum CombatData {
|
||||
Telemetry(TelemetryPacket),
|
||||
Control(ControlPacket),
|
||||
}
|
||||
|
||||
type CombatLogRow = (CombatData, u128);
|
||||
|
||||
pub async fn combat_logger(mut data: Receiver<CombatData>) -> anyhow::Result<()> {
|
||||
let mut path = storage();
|
||||
let time = chrono::offset::Utc::now();
|
||||
let formatted = time.to_rfc3339_opts(chrono::SecondsFormat::Secs, false);
|
||||
path.push(format!("{formatted}.combatlog"));
|
||||
let mut file = File::options().create(true).append(true).open(path).await?;
|
||||
|
||||
let mut save = Instant::now();
|
||||
|
||||
while let Some(packet) = data.recv().await {
|
||||
let time = SystemTime::now().duration_since(UNIX_EPOCH)?.as_micros();
|
||||
let vec = postcard::to_stdvec(&(packet, time))?;
|
||||
|
||||
file.write_u64(vec.len() as u64).await?;
|
||||
file.write_all(&vec).await?;
|
||||
|
||||
if save.elapsed() > Duration::from_secs(30) {
|
||||
save = Instant::now();
|
||||
file.sync_all().await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -3,6 +3,7 @@ use std::{fmt::format, ops::ControlFlow, result, sync::{atomic::Ordering, Arc},
|
|||
|
||||
use anyhow::{Context, Ok, Result};
|
||||
use atomic_float::AtomicF32;
|
||||
use combatlog::{combat_logger, CombatData};
|
||||
use common::{ControlPacket, TelemetryPacket};
|
||||
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
|
||||
use egui_toast::{Toast, ToastKind};
|
||||
|
@ -13,10 +14,13 @@ use tokio::{io::{AsyncReadExt, AsyncWriteExt, BufWriter, WriteHalf}, net::{tcp::
|
|||
|
||||
mod gui;
|
||||
mod storage_dir;
|
||||
mod combatlog;
|
||||
|
||||
pub const POWER_THRESHOLD: AtomicF32 = AtomicF32::new(DEFAULT_VOLUME_THRESHOLD);
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let (logging_sender, combatlog) = mpsc::channel(64);
|
||||
|
||||
// assumes pulseaudio system with f32 samples and 2204 sample packets
|
||||
dbg!(cpal::available_hosts());
|
||||
let host = cpal::default_host();
|
||||
|
@ -27,7 +31,6 @@ fn main() -> Result<()> {
|
|||
#[cfg(target_os = "linux")]
|
||||
let device = host.devices().unwrap().find(|d|d.name().unwrap() == "pulse").context("no pulse")?;
|
||||
|
||||
|
||||
let config = device.default_input_config()?;
|
||||
|
||||
dbg!(config.sample_format());
|
||||
|
@ -86,8 +89,15 @@ fn main() -> Result<()> {
|
|||
let spawner = executor.handle().clone();
|
||||
thread::spawn(move || {
|
||||
spawner.block_on(async {
|
||||
let log_toasts = toast_sender.clone();
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = combat_logger(combatlog).await {
|
||||
let _ = log_toasts.clone().send(Toast::new().text(format!("logger crashed: {e:?}")).kind(ToastKind::Error)).await;
|
||||
}
|
||||
});
|
||||
|
||||
loop {
|
||||
if let Err(e) = connect(toast_sender.clone(), notes.resubscribe(), data_sender.clone()).await {
|
||||
if let Err(e) = connect(toast_sender.clone(), notes.resubscribe(), data_sender.clone(), logging_sender.clone()).await {
|
||||
if let Err(_) = toast_sender.send(Toast::new().text(format!("{e:?}")).kind(ToastKind::Error)).await {
|
||||
break;
|
||||
};
|
||||
|
@ -108,7 +118,7 @@ fn main() -> Result<()> {
|
|||
/// frequency, volume
|
||||
type Detection = (Option<f32>, f32);
|
||||
|
||||
async fn connect(toast_sender: mpsc::Sender<Toast>, notes: broadcast::Receiver<Detection>, data_sender: watch::Sender<GUIData>) -> Result<()>{
|
||||
async fn connect(toast_sender: mpsc::Sender<Toast>, notes: broadcast::Receiver<Detection>, data_sender: watch::Sender<GUIData>, logging_sender: mpsc::Sender<CombatData>) -> Result<()>{
|
||||
toast_sender.send(Toast::new().text("connecting to bot").kind(ToastKind::Info)).await?;
|
||||
let cruisecontrol = TcpStream::connect("192.168.1.2:1234").await?;
|
||||
println!("connected");
|
||||
|
@ -116,13 +126,13 @@ async fn connect(toast_sender: mpsc::Sender<Toast>, notes: broadcast::Receiver<D
|
|||
cruisecontrol.set_nodelay(true)?;
|
||||
let (telem, control) = cruisecontrol.into_split();
|
||||
|
||||
tokio::spawn(telemetry_handler(telem, data_sender.clone()));
|
||||
tokio::spawn(telemetry_handler(telem, data_sender.clone(), logging_sender.clone()));
|
||||
|
||||
controller(notes, control, data_sender.clone()).await?;
|
||||
controller(notes, control, data_sender.clone(), logging_sender.clone()).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn telemetry_handler(mut telem: OwnedReadHalf, gui: watch::Sender<GUIData>) -> Result<()> {
|
||||
async fn telemetry_handler(mut telem: OwnedReadHalf, gui: watch::Sender<GUIData>, logging_sender: mpsc::Sender<CombatData>) -> Result<()> {
|
||||
let mut buf = vec![0; 2048];
|
||||
loop {
|
||||
let len = telem.read_u32().await.context("bad length")? as usize;
|
||||
|
@ -132,6 +142,8 @@ async fn telemetry_handler(mut telem: OwnedReadHalf, gui: watch::Sender<GUIData>
|
|||
|
||||
//println!("telem: {telem:?}");
|
||||
|
||||
logging_sender.send(CombatData::Telemetry(telem.clone())).await?;
|
||||
|
||||
gui.send_modify(|gui| {
|
||||
gui.telemetry = Some(telem);
|
||||
});
|
||||
|
@ -139,7 +151,7 @@ async fn telemetry_handler(mut telem: OwnedReadHalf, gui: watch::Sender<GUIData>
|
|||
}
|
||||
}
|
||||
|
||||
async fn controller(mut notes: broadcast::Receiver<Detection>, controller: OwnedWriteHalf, gui: watch::Sender<GUIData>) -> Result<()> {
|
||||
async fn controller(mut notes: broadcast::Receiver<Detection>, controller: OwnedWriteHalf, gui: watch::Sender<GUIData>, logging_sender: mpsc::Sender<CombatData>) -> Result<()> {
|
||||
let mut controller = BufWriter::new(controller);
|
||||
//send_packet(&mut controller, ControlPacket::Arm(true)).await?;
|
||||
//println!("armed flipper");
|
||||
|
@ -168,6 +180,7 @@ async fn controller(mut notes: broadcast::Receiver<Detection>, controller: Owned
|
|||
}
|
||||
|
||||
send_packet(&mut controller, control.clone()).await?;
|
||||
logging_sender.send(CombatData::Control(control.clone())).await?;
|
||||
gui.send_modify(|gui| gui.last_command = Some(control));
|
||||
} else {
|
||||
send_packet(&mut controller, ControlPacket::Twist(0.0, 0.0)).await?;
|
||||
|
|
157
interface/src/storage_dir.rs
Normal file
157
interface/src/storage_dir.rs
Normal file
|
@ -0,0 +1,157 @@
|
|||
// copied from eframe (private item)
|
||||
// https://docs.rs/eframe/latest/src/eframe/native/file_storage.rs.html#17
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use eframe::egui;
|
||||
|
||||
pub fn storage() -> PathBuf {
|
||||
storage_dir("cruisecontrol").unwrap()
|
||||
}
|
||||
|
||||
pub fn storage_dir(app_id: &str) -> Option<PathBuf> {
|
||||
|
||||
use egui::os::OperatingSystem as OS;
|
||||
|
||||
use std::env::var_os;
|
||||
|
||||
match OS::from_target_os() {
|
||||
|
||||
OS::Nix => var_os("XDG_DATA_HOME")
|
||||
|
||||
.map(PathBuf::from)
|
||||
|
||||
.filter(|p| p.is_absolute())
|
||||
|
||||
.or_else(|| home::home_dir().map(|p| p.join(".local").join("share")))
|
||||
|
||||
.map(|p| {
|
||||
|
||||
p.join(
|
||||
|
||||
app_id
|
||||
|
||||
.to_lowercase()
|
||||
|
||||
.replace(|c: char| c.is_ascii_whitespace(), ""),
|
||||
|
||||
)
|
||||
|
||||
}),
|
||||
|
||||
OS::Mac => home::home_dir().map(|p| {
|
||||
|
||||
p.join("Library")
|
||||
|
||||
.join("Application Support")
|
||||
|
||||
.join(app_id.replace(|c: char| c.is_ascii_whitespace(), "-"))
|
||||
|
||||
}),
|
||||
|
||||
OS::Windows => roaming_appdata().map(|p| p.join(app_id).join("data")),
|
||||
|
||||
OS::Unknown | OS::Android | OS::IOS => None,
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Adapted from
|
||||
|
||||
// https://github.com/rust-lang/cargo/blob/6e11c77384989726bb4f412a0e23b59c27222c34/crates/home/src/windows.rs#L19-L37
|
||||
|
||||
#[cfg(all(windows, not(target_vendor = "uwp")))]
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
|
||||
fn roaming_appdata() -> Option<PathBuf> {
|
||||
|
||||
use std::ffi::OsString;
|
||||
|
||||
use std::os::windows::ffi::OsStringExt;
|
||||
|
||||
use std::ptr;
|
||||
|
||||
use std::slice;
|
||||
|
||||
|
||||
use windows_sys::Win32::Foundation::S_OK;
|
||||
|
||||
use windows_sys::Win32::System::Com::CoTaskMemFree;
|
||||
|
||||
use windows_sys::Win32::UI::Shell::{
|
||||
|
||||
FOLDERID_RoamingAppData, SHGetKnownFolderPath, KF_FLAG_DONT_VERIFY,
|
||||
|
||||
};
|
||||
|
||||
|
||||
extern "C" {
|
||||
|
||||
fn wcslen(buf: *const u16) -> usize;
|
||||
|
||||
}
|
||||
|
||||
let mut path_raw = ptr::null_mut();
|
||||
|
||||
|
||||
// SAFETY: SHGetKnownFolderPath allocates for us, we don't pass any pointers to it.
|
||||
|
||||
// See https://learn.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shgetknownfolderpath
|
||||
|
||||
let result = unsafe {
|
||||
|
||||
SHGetKnownFolderPath(
|
||||
|
||||
&FOLDERID_RoamingAppData,
|
||||
|
||||
KF_FLAG_DONT_VERIFY as u32,
|
||||
|
||||
std::ptr::null_mut(),
|
||||
|
||||
&mut path_raw,
|
||||
|
||||
)
|
||||
|
||||
};
|
||||
|
||||
|
||||
let path = if result == S_OK {
|
||||
|
||||
// SAFETY: SHGetKnownFolderPath indicated success and is supposed to allocate a nullterminated string for us.
|
||||
|
||||
let path_slice = unsafe { slice::from_raw_parts(path_raw, wcslen(path_raw)) };
|
||||
|
||||
Some(PathBuf::from(OsString::from_wide(path_slice)))
|
||||
|
||||
} else {
|
||||
|
||||
None
|
||||
|
||||
};
|
||||
|
||||
|
||||
// SAFETY:
|
||||
|
||||
// This memory got allocated by SHGetKnownFolderPath, we didn't touch anything in the process.
|
||||
|
||||
// A null ptr is a no-op for `CoTaskMemFree`, so in case this failed we're still good.
|
||||
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-cotaskmemfree
|
||||
|
||||
unsafe { CoTaskMemFree(path_raw.cast()) };
|
||||
|
||||
|
||||
path
|
||||
|
||||
}
|
||||
|
||||
|
||||
#[cfg(any(not(windows), target_vendor = "uwp"))]
|
||||
|
||||
fn roaming_appdata() -> Option<PathBuf> {
|
||||
|
||||
None
|
||||
|
||||
}
|
Loading…
Reference in a new issue