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

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})");
impl<M: Mcp23017 + Debug> Mcp23017Task<M> {
pub fn new(mcp23017: M) -> Self {
trace!("Mcp23017Task::new(mcp23017: {mcp23017:?})");
Self {
mcp23017,
pins: AllPins::new(),
}
}
}
let (sender, receiver) = channel::<Mcp23017Message>();
let period = Duration::from_nanos(1_000_000_000 / frequency);
impl<M: Mcp23017> CyclicTask for Mcp23017Task<M> {
type Message = Mcp23017Message;
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;
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) {
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);
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);
}
}
})?;
Ok((
handle,
Self {
name,
sender,
}
))
}
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,52 +0,0 @@
use anyhow::Result;
use fern::colors::{Color, ColoredLevelConfig};
use log::debug;
use std::fs::create_dir_all;
use std::str::FromStr;
use std::{env, thread};
pub fn setup_logger() -> Result<()> {
let log_file = env::var("LOG_FILE").or_else(|_| {
create_dir_all("logs/")?;
anyhow::Ok(format!(
"logs/{}_{}.log",
env!("CARGO_PKG_NAME"),
chrono::Local::now().format("%Y%m%d_%H%M%S")
))
})?;
let log_level = match env::var("LOG_LEVEL") {
Ok(log_level) => log::LevelFilter::from_str(&log_level).unwrap_or(log::LevelFilter::Info),
Err(_) => log::LevelFilter::Info,
};
let colors = ColoredLevelConfig::new()
.error(Color::Red)
.warn(Color::Yellow)
.info(Color::White)
.debug(Color::White)
.trace(Color::BrightBlack);
fern::Dispatch::new()
.format(move |out, message, record| {
let binding = thread::current();
let thread_name = binding.name().unwrap_or("<unnamed>");
out.finish(format_args!(
"[{time} {level} {thread_name} {target}] {message}",
level = colors.color(record.level()),
time = chrono::Local::now().format("%Y-%m-%dT%H:%M:%S%.9f"),
target = record.target(),
))
})
.chain(
fern::Dispatch::new()
.level(log_level)
.chain(std::io::stdout()),
)
.chain(fern::log_file(log_file.clone())?)
.apply()?;
debug!("Logging to {} at level {}", log_file, log_level);
Ok(())
}

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,
})
}
}