initial comms

This commit is contained in:
2025-10-25 12:23:23 -07:00
parent fd63bdc0c9
commit b067ae5cec
17 changed files with 536 additions and 356 deletions

350
Cargo.lock generated
View File

@@ -17,15 +17,6 @@ version = "1.0.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
[[package]]
name = "approx"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6"
dependencies = [
"num-traits",
]
[[package]]
name = "autocfg"
version = "1.4.0"
@@ -44,12 +35,6 @@ version = "3.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
[[package]]
name = "bytemuck"
version = "1.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9134a6ef01ce4b366b50689c94f82c14bc72bc5d0386829828a2e2752ef7958c"
[[package]]
name = "cc"
version = "1.2.17"
@@ -80,10 +65,38 @@ dependencies = [
"iana-time-zone",
"js-sys",
"num-traits",
"serde",
"wasm-bindgen",
"windows-link",
]
[[package]]
name = "ciborium"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e"
dependencies = [
"ciborium-io",
"ciborium-ll",
"serde",
]
[[package]]
name = "ciborium-io"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757"
[[package]]
name = "ciborium-ll"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9"
dependencies = [
"ciborium-io",
"half",
]
[[package]]
name = "colored"
version = "2.2.0"
@@ -121,6 +134,12 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
[[package]]
name = "crunchy"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
[[package]]
name = "ctrlc"
version = "3.5.0"
@@ -208,100 +227,15 @@ dependencies = [
]
[[package]]
name = "glam"
version = "0.14.0"
name = "half"
version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "333928d5eb103c5d4050533cec0384302db6be8ef7d3cebd30ec6a35350353da"
[[package]]
name = "glam"
version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3abb554f8ee44336b72d522e0a7fe86a29e09f839a36022fa869a7dfe941a54b"
[[package]]
name = "glam"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4126c0479ccf7e8664c36a2d719f5f2c140fbb4f9090008098d2c291fa5b3f16"
[[package]]
name = "glam"
version = "0.17.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e01732b97afd8508eee3333a541b9f7610f454bb818669e66e90f5f57c93a776"
[[package]]
name = "glam"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "525a3e490ba77b8e326fb67d4b44b4bd2f920f44d4cc73ccec50adc68e3bee34"
[[package]]
name = "glam"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b8509e6791516e81c1a630d0bd7fbac36d2fa8712a9da8662e716b52d5051ca"
[[package]]
name = "glam"
version = "0.20.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f43e957e744be03f5801a55472f593d43fabdebf25a4585db250f04d86b1675f"
[[package]]
name = "glam"
version = "0.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "518faa5064866338b013ff9b2350dc318e14cc4fcd6cb8206d7e7c9886c98815"
[[package]]
name = "glam"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12f597d56c1bd55a811a1be189459e8fad2bbc272616375602443bdfb37fa774"
[[package]]
name = "glam"
version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e4afd9ad95555081e109fe1d21f2a30c691b5f0919c67dfa690a2e1eb6bd51c"
[[package]]
name = "glam"
version = "0.24.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5418c17512bdf42730f9032c74e1ae39afc408745ebb2acf72fbc4691c17945"
[[package]]
name = "glam"
version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "151665d9be52f9bb40fc7966565d39666f2d1e69233571b71b87791c7e0528b3"
[[package]]
name = "glam"
version = "0.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e05e7e6723e3455f4818c7b26e855439f7546cf617ef669d1adedb8669e5cb9"
[[package]]
name = "glam"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "779ae4bf7e8421cf91c0b3b64e7e8b40b862fba4d393f59150042de7c4965a94"
[[package]]
name = "glam"
version = "0.29.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8babf46d4c1c9d92deac9f7be466f76dfc4482b6452fc5024b5e8daf6ffeb3ee"
[[package]]
name = "glam"
version = "0.30.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e12d847aeb25f41be4c0ec9587d624e9cd631bc007a8fd7ce3f5851e064c6460"
checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b"
dependencies = [
"cfg-if",
"crunchy",
"zerocopy",
]
[[package]]
name = "hex"
@@ -361,62 +295,19 @@ version = "0.4.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
[[package]]
name = "matrixmultiply"
version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a06de3016e9fae57a36fd14dba131fccf49f74b40b7fbdb472f96e361ec71a08"
dependencies = [
"autocfg",
"rawpointer",
]
[[package]]
name = "nalgebra"
version = "0.34.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4d5b3eff5cd580f93da45e64715e8c20a3996342f1e466599cf7a267a0c2f5f"
dependencies = [
"approx",
"glam 0.14.0",
"glam 0.15.2",
"glam 0.16.0",
"glam 0.17.3",
"glam 0.18.0",
"glam 0.19.0",
"glam 0.20.5",
"glam 0.21.3",
"glam 0.22.0",
"glam 0.23.0",
"glam 0.24.2",
"glam 0.25.0",
"glam 0.27.0",
"glam 0.28.0",
"glam 0.29.3",
"glam 0.30.8",
"matrixmultiply",
"nalgebra-macros",
"num-complex 0.4.6",
"num-rational 0.4.2",
"num-traits",
"simba",
"typenum",
]
[[package]]
name = "nalgebra-macros"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "973e7178a678cfd059ccec50887658d482ce16b0aa9da3888ddeab5cd5eb4889"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "nautilus_common"
version = "0.1.0"
dependencies = [
"anyhow",
"chrono",
"ciborium",
"ctrlc",
"fern",
"log",
"serde",
"thiserror",
]
[[package]]
name = "nautilus_flight"
@@ -424,23 +315,26 @@ version = "0.0.1"
dependencies = [
"anyhow",
"chrono",
"ciborium",
"crc",
"ctrlc",
"embedded-hal 1.0.0",
"embedded-hal-bus",
"embedded-hal-mock",
"fern",
"hex",
"log",
"nalgebra",
"num-traits",
"nautilus_common",
"rpi-pal",
"thiserror",
]
[[package]]
name = "nautilus_ground"
version = "0.1.0"
dependencies = [
"anyhow",
"ciborium",
"log",
"nautilus_common",
]
[[package]]
name = "nb"
@@ -475,20 +369,10 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b7a8e9be5e039e2ff869df49155f1c06bd01ade2117ec783e56ab0932b67a8f"
dependencies = [
"num-complex 0.3.1",
"num-complex",
"num-integer",
"num-iter",
"num-rational 0.3.2",
"num-traits",
]
[[package]]
name = "num-bigint"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
dependencies = [
"num-integer",
"num-rational",
"num-traits",
]
@@ -501,15 +385,6 @@ dependencies = [
"num-traits",
]
[[package]]
name = "num-complex"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
dependencies = [
"num-traits",
]
[[package]]
name = "num-integer"
version = "0.1.46"
@@ -541,17 +416,6 @@ dependencies = [
"num-traits",
]
[[package]]
name = "num-rational"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
dependencies = [
"num-bigint",
"num-integer",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.19"
@@ -567,12 +431,6 @@ version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "paste"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
[[package]]
name = "proc-macro2"
version = "1.0.94"
@@ -591,12 +449,6 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "rawpointer"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3"
[[package]]
name = "rpi-pal"
version = "0.22.2"
@@ -619,12 +471,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2"
[[package]]
name = "safe_arch"
version = "0.7.4"
name = "serde"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96b02de82ddbe1b636e6170c21be622223aea188ef2e139be0a5b219ec215323"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
"bytemuck",
"serde_core",
"serde_derive",
]
[[package]]
name = "serde_core"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
@@ -633,19 +506,6 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "simba"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3a386a501cd104797982c15ae17aafe8b9261315b5d07e3ec803f2ea26be0fa"
dependencies = [
"approx",
"num-complex 0.4.6",
"num-traits",
"paste",
"wide",
]
[[package]]
name = "spin_sleep"
version = "1.3.1"
@@ -686,12 +546,6 @@ dependencies = [
"syn",
]
[[package]]
name = "typenum"
version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
[[package]]
name = "unicode-ident"
version = "1.0.18"
@@ -762,16 +616,6 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "wide"
version = "0.7.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41b5576b9a81633f3e8df296ce0063042a73507636cbe956c61133dd7034ab22"
dependencies = [
"bytemuck",
"safe_arch",
]
[[package]]
name = "windows-core"
version = "0.52.0"
@@ -868,3 +712,23 @@ name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "zerocopy"
version = "0.8.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831"
dependencies = [
"proc-macro2",
"quote",
"syn",
]

View File

@@ -4,3 +4,11 @@ version = "0.1.0"
edition = "2024"
[dependencies]
anyhow = "1.0.100"
fern = { version = "0.7.1", features = ["colored"] }
log = { version = "0.4.28", features = ["max_level_trace", "release_max_level_debug"] }
chrono = { version = "0.4.42", features = ["serde"] }
ctrlc = "3.5.0"
serde = { version = "1.0.228", features = ["derive"], default-features = false }
ciborium = { version = "0.2.2" }
thiserror = "2.0.17"

View File

@@ -0,0 +1,6 @@
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum Command {
Shutdown,
}

View File

@@ -1,14 +1,16 @@
pub fn add(left: u64, right: u64) -> u64 {
left + right
}
use log::info;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
#[cfg(test)]
mod tests {
use super::*;
pub mod logger;
pub mod command;
pub mod telemetry;
pub mod udp;
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
pub fn add_ctrlc_handler(flag: Arc<AtomicBool>) -> anyhow::Result<()> {
ctrlc::set_handler(move || {
info!("Shutdown Requested");
flag.store(false, Ordering::Relaxed);
})?;
Ok(())
}

View File

@@ -5,13 +5,13 @@ use std::fs::create_dir_all;
use std::str::FromStr;
use std::{env, thread};
pub fn setup_logger() -> Result<()> {
pub fn setup_logger(package_name: &'static str) -> Result<()> {
let log_file = env::var("LOG_FILE").or_else(|_| {
create_dir_all("logs/")?;
anyhow::Ok(format!(
"logs/{}_{}.log",
env!("CARGO_PKG_NAME"),
package_name,
chrono::Local::now().format("%Y%m%d_%H%M%S")
))
})?;

View File

@@ -0,0 +1,8 @@
use chrono::serde::ts_nanoseconds;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum Telemetry {
Timestamp(#[serde(with = "ts_nanoseconds")] DateTime<Utc>)
}

80
common/src/udp.rs Normal file
View File

@@ -0,0 +1,80 @@
use crate::udp::UdpSendCborError::LengthMismatch;
use log::error;
use serde::de::DeserializeOwned;
use serde::Serialize;
use std::io::{Cursor, ErrorKind};
use std::net::{SocketAddr, ToSocketAddrs, UdpSocket};
use thiserror::Error;
#[derive(Error, Debug)]
pub enum UdpRecvCborError {
#[error("IO Error")]
Io(#[from] std::io::Error),
#[error("Deserialization Error")]
Deserialization(#[from] ciborium::de::Error<std::io::Error>),
#[error("No Data")]
NoData,
}
#[derive(Error, Debug)]
pub enum UdpSendCborError {
#[error("IO Error")]
Io(#[from] std::io::Error),
#[error("Serialization Error")]
Serialization(#[from] ciborium::ser::Error<std::io::Error>),
#[error("Length Mismatch")]
LengthMismatch {
expected: usize,
actual: usize,
},
}
pub trait UdpSocketExt {
fn recv_cbor<T: DeserializeOwned, const N: usize>(&self, buffer: &mut Cursor<[u8; N]>) -> Result<(T, SocketAddr), UdpRecvCborError>;
fn send_cbor<T: Serialize + ?Sized, A: ToSocketAddrs, const N: usize>(&self, data: &T, buffer: &mut Cursor<[u8; N]>, addr: A) -> Result<(), UdpSendCborError>;
}
impl UdpSocketExt for UdpSocket {
fn recv_cbor<T: DeserializeOwned, const N: usize>(&self, buffer: &mut Cursor<[u8; N]>) -> Result<(T, SocketAddr), UdpRecvCborError> {
buffer.set_position(0);
match self.recv_from(buffer.get_mut()) {
Ok((size, addr)) => {
match ciborium::from_reader::<T, _>(&buffer.get_ref()[..size]) {
Ok(res) => Ok((res, addr)),
Err(err) => Err(err.into()),
}
}
Err(err) => {
match err.kind() {
ErrorKind::WouldBlock | ErrorKind::TimedOut => {
Err(UdpRecvCborError::NoData)
}
_ => Err(err.into())
}
}
}
}
fn send_cbor<T: Serialize + ?Sized, A: ToSocketAddrs, const N: usize>(&self, data: &T, mut buffer: &mut Cursor<[u8; N]>, addr: A) -> Result<(), UdpSendCborError> {
buffer.set_position(0);
match ciborium::into_writer(data, &mut buffer) {
Ok(_) => match self.send_to(&buffer.get_ref()[..buffer.position() as usize], addr) {
Ok(size) => {
if buffer.position() as usize != size {
return Err(LengthMismatch {
expected: buffer.position() as usize,
actual: size,
});
}
Ok(())
}
Err(e) => {
Err(e.into())
}
}
Err(e) => {
Err(e.into())
}
}
}
}

View File

@@ -5,19 +5,16 @@ edition = "2024"
[dependencies]
anyhow = "1.0.100"
fern = { version = "0.7.1", features = ["colored"] }
log = { version = "0.4.28", features = ["max_level_trace", "release_max_level_debug"] }
chrono = "0.4.42"
embedded-hal = "1.0.0"
embedded-hal-bus = { version = "0.3.0", features = ["std"] }
embedded-hal-mock = { version = "0.11.1", optional = true }
rpi-pal = { version = "0.22.2", features = ["hal"], optional = true }
nalgebra = "0.34.1"
hex = "0.4.3"
thiserror = "2.0.17"
num-traits = "0.2.19"
crc = "3.3.0"
ctrlc = { version = "3.5" }
nautilus_common = { path = "../common" }
ciborium = { version = "0.2.2" }
[dev-dependencies]
embedded-hal-mock = { version = "0.11.1" }

64
flight/src/comms/mod.rs Normal file
View File

@@ -0,0 +1,64 @@
use crate::scheduler::CyclicTask;
use anyhow::Result;
use log::{error, trace};
use nautilus_common::command::Command;
use nautilus_common::telemetry::Telemetry;
use nautilus_common::udp::{UdpRecvCborError, UdpSocketExt};
use std::fmt::Debug;
use std::io::Cursor;
use std::net::{IpAddr, Ipv4Addr, SocketAddr, ToSocketAddrs, UdpSocket};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::Receiver;
use std::sync::Arc;
use std::time::Instant;
#[derive(Debug)]
pub struct CommsTask<A: ToSocketAddrs> {
udp: UdpSocket,
ground_address: A,
running: Arc<AtomicBool>,
}
impl<A: ToSocketAddrs + Debug> CommsTask<A> {
pub fn new(
local_port: u16,
ground_address: A,
running: Arc<AtomicBool>,
) -> Result<Self> {
trace!("CommsTask::new(local_port: {local_port}, ground_address: {ground_address:?})");
let bind_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), local_port);
// let bind_addr = SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), local_port);
let udp = UdpSocket::bind(bind_addr)?;
udp.set_nonblocking(true)?;
Ok(Self {
udp,
ground_address,
running,
})
}
}
impl<A: ToSocketAddrs> CyclicTask for CommsTask<A> {
type Message = ();
fn step(&mut self, _receiver: &Receiver<Self::Message>, _step_time: Instant) {
let mut buffer = Cursor::new([0u8; 512]);
match self.udp.recv_cbor::<Command, _>(&mut buffer) {
Ok((cmd, _)) => {
match cmd {
Command::Shutdown => self.running.store(false, Ordering::Relaxed),
}
}
Err(UdpRecvCborError::NoData) => {}
Err(err) => {
error!("Rx error: {err}");
}
}
let tlm = Telemetry::Timestamp(chrono::Utc::now());
if let Err(err) = self.udp.send_cbor(&tlm, &mut buffer, &self.ground_address) {
error!("Tx Error: {err}");
}
}
}

View File

@@ -1,14 +1,11 @@
use crate::hardware::mcp23017::Mcp23017;
use crate::hardware::pin::PinDevice;
use anyhow::Result;
use crate::scheduler::{CyclicTask, TaskHandle};
use embedded_hal::digital::PinState;
use log::trace;
use std::fmt::Debug;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::{channel, Sender};
use std::thread;
use std::thread::{sleep, Scope, ScopedJoinHandle};
use std::time::{Duration, Instant};
use std::fmt::{Debug, Formatter};
use std::sync::mpsc::Receiver;
use std::time::Instant;
#[derive(Clone, Debug)]
pub enum Mcp23017Message {
@@ -20,14 +17,7 @@ pub enum Mcp23017Message {
},
}
#[derive(Clone, Debug)]
pub struct Mcp23017Task {
#[allow(dead_code)]
name: String,
sender: Sender<Mcp23017Message>,
}
impl PinDevice for Mcp23017Task {
impl PinDevice for TaskHandle<Mcp23017Message> {
fn set_pin(&self, pin: u8, value: PinState, valid_until: Instant, priority: u8) {
trace!("Mcp23017Task::set_pin(self: {self:?}, pin: {pin}, value: {value:?})");
// This can only fail if the other end is disconnected - which we intentionally want to
@@ -41,6 +31,17 @@ impl PinDevice for Mcp23017Task {
}
}
pub struct Mcp23017Task<M: Mcp23017> {
mcp23017: M,
pins: AllPins,
}
impl<M: Mcp23017 + Debug> Debug for Mcp23017Task<M> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "Mcp23017Task {{ mcp23017: {:?} }}", self.mcp23017)
}
}
struct AllPins {
pins: [PinData; 16],
}
@@ -137,67 +138,48 @@ impl PinData {
}
}
impl Mcp23017Task {
pub fn start<'a, M: Mcp23017 + Send + Debug>(
scope: &'a Scope<'a, '_>,
running: &'a AtomicBool,
mut mcp23017: M,
name: String,
frequency: u64,
) -> Result<(ScopedJoinHandle<'a, ()>, Self)> where
M: 'a,
{
trace!("Mcp23017Task::start(scope, running, mcp23017: {mcp23017:?}, name: {name}, frequency: {frequency})");
let (sender, receiver) = channel::<Mcp23017Message>();
let period = Duration::from_nanos(1_000_000_000 / frequency);
let handle = thread::Builder::new()
.name(name.clone())
.spawn_scoped(scope, move || {
let mut pins = AllPins::new();
let mut cycle_start_time = Instant::now();
while running.load(Ordering::Relaxed) {
let mut changed = false;
while let Ok(recv) = receiver.try_recv() {
match recv {
Mcp23017Message::SetPin { pin, value, valid_until, priority } => {
if (0u8..16u8).contains(&pin) {
pins.pins[pin as usize].set(value, valid_until, priority);
}
}
}
}
for pin in 0u8..16u8 {
// This shouldn't be able to fail
// TODO: handle error case
let state = pins.pins[pin as usize].get(cycle_start_time);
if pins.pins[pin as usize].changed {
pins.pins[pin as usize].changed = false;
let _ = mcp23017.set_pin(pin, state);
changed = true;
}
}
if changed {
let _ = mcp23017.flush();
}
cycle_start_time += period;
let sleep_duration = cycle_start_time - Instant::now();
if sleep_duration > Duration::ZERO {
sleep(sleep_duration);
}
}
})?;
Ok((
handle,
Self {
name,
sender,
}
))
impl<M: Mcp23017 + Debug> Mcp23017Task<M> {
pub fn new(mcp23017: M) -> Self {
trace!("Mcp23017Task::new(mcp23017: {mcp23017:?})");
Self {
mcp23017,
pins: AllPins::new(),
}
}
}
impl<M: Mcp23017> CyclicTask for Mcp23017Task<M> {
type Message = Mcp23017Message;
fn step(
&mut self,
receiver: &Receiver<Self::Message>,
step_time: Instant,
) {
let mut changed = false;
while let Ok(recv) = receiver.try_recv() {
match recv {
Mcp23017Message::SetPin { pin, value, valid_until, priority } => {
if (0u8..16u8).contains(&pin) {
self.pins.pins[pin as usize].set(value, valid_until, priority);
}
}
}
}
for pin in 0u8..16u8 {
// This shouldn't be able to fail
// TODO: handle error case
let state = self.pins.pins[pin as usize].get(step_time);
if self.pins.pins[pin as usize].changed {
self.pins.pins[pin as usize].changed = false;
let _ = self.mcp23017.set_pin(pin, state);
changed = true;
}
}
if changed {
let _ = self.mcp23017.flush();
}
}
}

View File

@@ -44,6 +44,7 @@ impl RaspiHardware {
Mode::Mode0,
)?), 3.3f64).into(),
mct8316a: SimMct8316a::new().into(),
// mct8316a: SimMct8316a::new().into(),
})
}
}

View File

@@ -1,30 +1,23 @@
use crate::comms::CommsTask;
use crate::hardware::channelization::{LED_A, LED_B};
use crate::hardware::initialize;
use crate::hardware::mcp23017::{Mcp23017, Mcp23017Task};
use crate::hardware::mct8316a::Mct8316a;
use crate::hardware::pin::Pin;
use crate::hardware::Hardware;
use crate::on_drop::on_drop;
use crate::scheduler::Scheduler;
use anyhow::Result;
use embedded_hal::digital::PinState;
use embedded_hal::pwm::SetDutyCycle;
use log::{debug, info};
use nautilus_common::add_ctrlc_handler;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::thread;
use std::thread::sleep;
use std::time::{Duration, Instant};
mod hardware;
fn add_ctrlc_handler(flag: Arc<AtomicBool>) -> Result<()> {
ctrlc::set_handler(move || {
info!("Shutdown Requested");
flag.store(false, Ordering::Relaxed);
})?;
Ok(())
}
pub fn run() -> Result<()> {
info!(
"Project Nautilus Flight Software {}",
@@ -49,21 +42,17 @@ pub fn run() -> Result<()> {
mcp23017_b.init()?;
mct8316.init()?;
thread::scope(|scope| {
// This will automatically set running to false when it drops
// This means that if the main thread exits this scope, we will
// shut down any side branches
let _shutdown_threads = on_drop(|| running.store(false, Ordering::Relaxed));
Scheduler::new(running.clone(), |s| {
let task_a = s.run_cyclic("mcp23017-a", Mcp23017Task::new(mcp23017_a), 10)?;
let task_b = s.run_cyclic("mcp23017-b", Mcp23017Task::new(mcp23017_b), 10)?;
let (_, task_a) = Mcp23017Task::start(scope, &running, mcp23017_a, "mcp23017-a".into(), 10)?;
let (_, task_b) = Mcp23017Task::start(scope, &running, mcp23017_b, "mcp23017-b".into(), 10)?;
let _comms = s.run_cyclic("comms", CommsTask::new(15000, "192.168.50.157:14000", running.clone())?, 1)?;
let mut led_pin_a = LED_A.new(&task_a, &task_b)?;
let mut led_pin_b = LED_B.new(&task_a, &task_b)?;
info!("Starting Main Loop");
for _ in 0..2 {
loop {
debug!("A On");
led_pin_a.set(PinState::High, Instant::now() + Duration::from_secs(2), 0);
sleep(Duration::from_secs(1));
@@ -103,3 +92,5 @@ mod test_utils;
mod data;
mod on_drop;
mod rcs;
mod comms;
mod scheduler;

View File

@@ -1,11 +1,10 @@
use crate::logger::setup_logger;
use log::error;
use nautilus_common::logger::setup_logger;
use nautilus_flight::run;
mod logger;
fn main() {
setup_logger().expect("Failed to setup logger");
setup_logger(env!("CARGO_PKG_NAME")).expect("Failed to setup logger");
match run() {
Ok(_) => {}
Err(err) => {

114
flight/src/scheduler/mod.rs Normal file
View File

@@ -0,0 +1,114 @@
use crate::on_drop::on_drop;
use anyhow::Result;
use log::trace;
use std::fmt::Debug;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::{channel, Receiver, Sender};
use std::sync::Arc;
use std::thread;
use std::thread::{sleep, Scope};
use std::time::{Duration, Instant};
#[derive(Clone, Debug)]
pub struct TaskHandle<Message> {
#[allow(dead_code)]
pub name: String,
pub sender: Sender<Message>,
}
#[allow(dead_code)]
pub trait Task {
type Message;
fn run(self, receiver: Receiver<Self::Message>, running: Arc<AtomicBool>);
}
pub trait CyclicTask {
type Message;
fn step(&mut self, receiver: &Receiver<Self::Message>, step_time: Instant);
}
pub struct Scheduler<'s, 'e>
{
scope: &'s Scope<'s, 'e>,
running: Arc<AtomicBool>,
}
impl<'s, 'e> Scheduler<'s, 'e> {
pub fn new<'env, F, R>(running: Arc<AtomicBool>, f: F) -> R
where
F: FnOnce(Scheduler<'_, 'env>) -> R,
{
trace!("Scheduler::new(running: {running:?}, f)");
thread::scope(|scope: &Scope| {
// This will automatically set running to false when it drops
// This means that if the function returns any side branches
// checking running will shut down
let _shutdown_threads = on_drop(|| running.store(false, Ordering::Relaxed));
f(Scheduler {
scope,
running: running.clone(),
})
})
}
#[allow(dead_code)]
pub fn run<T>(
&self,
name: impl Into<String>,
task: T,
) -> Result<TaskHandle<T::Message>> where
T: Task + Send + Debug + 's,
T::Message: Send,
{
let name = name.into();
trace!("Scheduler::run(name: {name}, task: {task:?})");
let running = self.running.clone();
let (sender, receiver) = channel::<T::Message>();
let _ = thread::Builder::new()
.name(name.clone())
.spawn_scoped(self.scope, move || {
task.run(receiver, running);
})?;
Ok(TaskHandle {
name,
sender,
})
}
pub fn run_cyclic<T>(
&self,
name: impl Into<String>,
mut task: T,
frequency: u64,
) -> Result<TaskHandle<T::Message>> where
T: CyclicTask + Send + Debug + 's,
T::Message: Send,
{
let name = name.into();
trace!("Scheduler::run_cyclic(name: {name}, task: {task:?}, frequency: {frequency})");
let running = self.running.clone();
let (sender, receiver) = channel::<T::Message>();
let _ = thread::Builder::new()
.name(name.clone())
.spawn_scoped(self.scope, move || {
let period = Duration::from_nanos(1_000_000_000 / frequency);
let mut cycle_start_time = Instant::now();
while running.load(Ordering::Relaxed) {
task.step(&receiver, cycle_start_time);
cycle_start_time += period;
let sleep_duration = cycle_start_time - Instant::now();
if sleep_duration > Duration::ZERO {
sleep(sleep_duration);
}
}
})?;
Ok(TaskHandle {
name,
sender,
})
}
}

View File

@@ -4,3 +4,7 @@ version = "0.1.0"
edition = "2024"
[dependencies]
anyhow = "1.0.100"
nautilus_common = { path = "../common" }
log = "0.4.28"
ciborium = { version = "0.2.2" }

50
ground/src/lib.rs Normal file
View File

@@ -0,0 +1,50 @@
use anyhow::Result;
use log::{error, info};
use nautilus_common::add_ctrlc_handler;
use nautilus_common::command::Command;
use nautilus_common::telemetry::Telemetry;
use nautilus_common::udp::{UdpRecvCborError, UdpSocketExt};
use std::io::Cursor;
use std::net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::time::Duration;
pub fn run() -> Result<()> {
info!(
"Project Nautilus Ground Software {}",
env!("CARGO_PKG_VERSION")
);
let running = Arc::new(AtomicBool::new(true));
add_ctrlc_handler(running.clone())?;
let mut flight_addr = None;
let bind_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 14000);
let udp = UdpSocket::bind(bind_addr)?;
udp.set_read_timeout(Some(Duration::from_millis(100)))?;
let mut buffer = Cursor::new([0u8; 512]);
while running.load(Ordering::Relaxed) {
match udp.recv_cbor::<Telemetry, _>(&mut buffer) {
Ok((tlm, addr)) => {
flight_addr = Some(addr);
info!("{tlm:?}");
}
Err(UdpRecvCborError::NoData) => {
// NoOp
}
Err(err) => {
error!("Rx error: {err}");
}
}
}
if let Some(flight_addr) = flight_addr {
let cmd = Command::Shutdown;
udp.send_cbor(&cmd, &mut buffer, &flight_addr)?;
}
Ok(())
}

View File

@@ -1,3 +1,13 @@
use log::error;
use nautilus_common::logger::setup_logger;
use nautilus_ground::run;
fn main() {
println!("Hello, world!");
setup_logger(env!("CARGO_PKG_NAME")).expect("Failed to setup logger");
match run() {
Ok(_) => {}
Err(err) => {
error!("An unhandled error occurred: {}\n\n{}", err, err.backtrace());
}
}
}