adds initial rcs implementation

This commit is contained in:
2026-01-05 20:26:54 -05:00
parent 252db5993d
commit 98541737a1
23 changed files with 1490 additions and 229 deletions

View File

@@ -7,7 +7,7 @@ edition = "2024"
anyhow = { workspace = true }
chrono = { workspace = true }
ctrlc = { workspace = true }
derive_more = {workspace = true, features = ["display", "from"]}
derive_more = {workspace = true, features = ["display", "from", "add", "add_assign", "not"]}
fern = { workspace = true }
log = { workspace = true }
postcard = { workspace = true }

View File

@@ -1,48 +1,10 @@
use chrono::serde::ts_nanoseconds;
use chrono::{DateTime, TimeDelta, Utc};
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use std::fmt::Debug;
use std::ops::{Deref, DerefMut};
use std::time::Instant;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SetPin {
pub pin: u8,
pub value: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ValidPriorityCommand<T>
where
T: Clone + Debug,
{
pub inner: T,
#[serde(with = "ts_nanoseconds")]
pub valid_until: DateTime<Utc>,
pub priority: u8,
}
impl<T> ValidPriorityCommand<T>
where
T: Clone + Debug,
{
/// Get the valid until time as an Instant
///
/// # Panics
/// While this theoretically could panic, there are checks to prevent this.
pub fn get_valid_until_instant(&self) -> Instant {
let delta = self.valid_until.signed_duration_since(Utc::now());
let now = Instant::now();
if delta >= TimeDelta::zero() {
// Unwrap is safe because we checked that it is not negative
now + delta.to_std().unwrap()
} else {
// Unwrap is safe because we converted the negative to a positive
now.checked_sub((-delta).to_std().unwrap()).unwrap_or(now)
}
}
}
pub mod set_pin;
pub mod set_rcs;
pub mod valid_priority_command;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct CommandHeader<'a> {
@@ -59,26 +21,3 @@ pub struct OwnedCommandHeader {
pub trait Command: Serialize + DeserializeOwned {}
impl Command for () {}
impl Command for SetPin {}
impl<T: Clone + Debug + Command + Serialize + DeserializeOwned> Command
for ValidPriorityCommand<T>
{
}
impl<T: Clone + Debug + Command + Serialize + DeserializeOwned> Deref for ValidPriorityCommand<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl<T: Clone + Debug + Command + Serialize + DeserializeOwned> DerefMut
for ValidPriorityCommand<T>
{
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}

View File

@@ -0,0 +1,10 @@
use crate::command::Command;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SetPin {
pub pin: u8,
pub value: bool,
}
impl Command for SetPin {}

View File

@@ -0,0 +1,11 @@
use crate::command::Command;
use crate::math::Vector;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SetRcs {
pub translation: Vector,
pub rotation: Vector,
}
impl Command for SetRcs {}

View File

@@ -0,0 +1,61 @@
use crate::command::Command;
use chrono::serde::ts_nanoseconds;
use chrono::{DateTime, TimeDelta, Utc};
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use std::fmt::Debug;
use std::ops::{Deref, DerefMut};
use std::time::Instant;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ValidPriorityCommand<T>
where
T: Clone + Debug,
{
pub inner: T,
#[serde(with = "ts_nanoseconds")]
pub valid_until: DateTime<Utc>,
pub priority: u8,
}
impl<T: Clone + Debug + Command + Serialize + DeserializeOwned> Command
for ValidPriorityCommand<T>
{
}
impl<T> ValidPriorityCommand<T>
where
T: Clone + Debug,
{
/// Get the valid until time as an Instant
///
/// # Panics
/// While this theoretically could panic, there are checks to prevent this.
pub fn get_valid_until_instant(&self) -> Instant {
let delta = self.valid_until.signed_duration_since(Utc::now());
let now = Instant::now();
if delta >= TimeDelta::zero() {
// Unwrap is safe because we checked that it is not negative
now + delta.to_std().unwrap()
} else {
// Unwrap is safe because we converted the negative to a positive
now.checked_sub((-delta).to_std().unwrap()).unwrap_or(now)
}
}
}
impl<T: Clone + Debug + Command + Serialize + DeserializeOwned> Deref for ValidPriorityCommand<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl<T: Clone + Debug + Command + Serialize + DeserializeOwned> DerefMut
for ValidPriorityCommand<T>
{
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}

View File

@@ -3,6 +3,7 @@ use log::info;
pub mod command;
pub mod logger;
pub mod math;
pub mod on_drop;
pub mod telemetry;
pub mod udp;

View File

@@ -43,12 +43,13 @@ pub fn setup_logger(package_name: &'static str) -> Result<()> {
target = record.target(),
));
})
.level_for("tungstenite", LevelFilter::Warn)
.level_for("tokio_tungstenite", LevelFilter::Warn)
.level_for("microlp", LevelFilter::Warn)
.level_for("api", LevelFilter::Info)
.chain(
fern::Dispatch::new()
.level(log_level)
.level_for("tungstenite", LevelFilter::Warn)
.level_for("tokio_tungstenite", LevelFilter::Warn)
.level_for("api", LevelFilter::Info)
.chain(std::io::stdout()),
)
.chain(fern::log_file(log_file.clone())?)

196
common/src/math/mod.rs Normal file
View File

@@ -0,0 +1,196 @@
use derive_more::{Add, AddAssign, Display, Neg, Sub, SubAssign};
use serde::{Deserialize, Serialize};
use std::ops::{Div, DivAssign, Mul, MulAssign};
#[derive(
Debug,
Copy,
Clone,
Add,
AddAssign,
Sub,
SubAssign,
PartialEq,
Display,
Neg,
Serialize,
Deserialize,
)]
#[display("({x}, {y}, {z})")]
pub struct Vector {
pub x: f64,
pub y: f64,
pub z: f64,
}
impl Vector {
pub const ZERO: Vector = Vector {
x: 0.0,
y: 0.0,
z: 0.0,
};
pub const X: Vector = Vector {
x: 1.0,
y: 0.0,
z: 0.0,
};
pub const Y: Vector = Vector {
x: 0.0,
y: 1.0,
z: 0.0,
};
pub const Z: Vector = Vector {
x: 0.0,
y: 0.0,
z: 1.0,
};
#[must_use]
#[inline]
pub const fn new(x: f64, y: f64, z: f64) -> Self {
Self { x, y, z }
}
#[must_use]
#[inline]
pub const fn dot_product(self, rhs: Self) -> f64 {
self.x * rhs.x + self.y * rhs.y + self.z * rhs.z
}
#[must_use]
#[inline]
pub const fn cross_product(self, rhs: Self) -> Self {
Self {
x: self.y * rhs.z - self.z * rhs.y,
y: self.z * rhs.x - self.x * rhs.z,
z: self.x * rhs.y - self.y * rhs.x,
}
}
#[must_use]
#[inline]
pub const fn length2(self) -> f64 {
self.dot_product(self)
}
#[must_use]
#[inline]
pub fn length(self) -> f64 {
self.length2().sqrt()
}
#[must_use]
#[inline]
pub fn normalize(self) -> Self {
self / self.length()
}
#[must_use]
#[inline]
pub fn with_length(self, length: f64) -> Self {
self.normalize() * length
}
#[must_use]
#[inline]
pub const fn project_onto_vector(self, vector: Self) -> Self {
let scale_factor = self.dot_product(vector) / vector.length2();
// Manual multiplication to allow const-ification
Self {
x: vector.x * scale_factor,
y: vector.y * scale_factor,
z: vector.z * scale_factor,
}
}
#[must_use]
#[inline]
pub fn project_onto_plane(self, vector: Self) -> Self {
self - self.project_onto_vector(vector)
}
}
impl Default for Vector {
#[inline]
fn default() -> Self {
Self::ZERO
}
}
impl<T> Mul<T> for Vector
where
f64: Mul<T, Output = f64>,
T: Copy,
{
type Output = Self;
#[inline]
fn mul(self, rhs: T) -> Self::Output {
Self::Output {
x: self.x * rhs,
y: self.y * rhs,
z: self.z * rhs,
}
}
}
impl<T> MulAssign<T> for Vector
where
f64: MulAssign<T>,
T: Copy,
{
#[inline]
fn mul_assign(&mut self, rhs: T) {
self.x *= rhs;
self.y *= rhs;
self.z *= rhs;
}
}
impl Mul<Vector> for f64 {
type Output = Vector;
#[inline]
fn mul(self, rhs: Vector) -> Self::Output {
rhs * self
}
}
impl<T> Div<T> for Vector
where
f64: Div<T, Output = f64>,
T: Copy,
{
type Output = Self;
#[inline]
fn div(self, rhs: T) -> Self::Output {
Self::Output {
x: self.x / rhs,
y: self.y / rhs,
z: self.z / rhs,
}
}
}
impl<T> DivAssign<T> for Vector
where
f64: DivAssign<T>,
T: Copy,
{
#[inline]
fn div_assign(&mut self, rhs: T) {
self.x /= rhs;
self.y /= rhs;
self.z /= rhs;
}
}
impl Mul for Vector {
type Output = f64;
#[inline]
fn mul(self, rhs: Self) -> f64 {
self.dot_product(rhs)
}
}