start integrating sim in example
Some checks failed
Rust/sat-rs/pipeline/head There was a failure building this commit

This commit is contained in:
Robin Müller 2024-05-08 20:38:45 +02:00
parent 3746e9ebb0
commit c20163b10a
Signed by: muellerr
GPG Key ID: A649FB78196E3849
13 changed files with 510 additions and 205 deletions

View File

@ -27,6 +27,9 @@ serde_json = "1"
path = "../satrs" path = "../satrs"
features = ["test_util"] features = ["test_util"]
[dependencies.satrs-minisim]
path = "../satrs-minisim"
[dependencies.satrs-mib] [dependencies.satrs-mib]
version = "0.1.1" version = "0.1.1"
path = "../satrs-mib" path = "../satrs-mib"

View File

@ -4,7 +4,10 @@ use satrs::queue::{GenericSendError, GenericTargetedMessagingError};
use satrs::spacepackets::ecss::hk; use satrs::spacepackets::ecss::hk;
use satrs::spacepackets::ecss::tm::{PusTmCreator, PusTmSecondaryHeader}; use satrs::spacepackets::ecss::tm::{PusTmCreator, PusTmSecondaryHeader};
use satrs::spacepackets::SpHeader; use satrs::spacepackets::SpHeader;
use satrs_example::{DeviceMode, TimeStampHelper}; use satrs_example::{DeviceMode, TimestampHelper};
use satrs_minisim::acs::lis3mdl::{FIELD_LSB_PER_GAUSS_4_SENS, GAUSS_TO_MICROTESLA_FACTOR};
use satrs_minisim::acs::MgmRequestLis3Mdl;
use satrs_minisim::{SimReply, SimRequest};
use std::sync::mpsc::{self}; use std::sync::mpsc::{self};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
@ -20,9 +23,12 @@ use crate::requests::CompositeRequest;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
const GAUSS_TO_MICROTESLA_FACTOR: f32 = 100.0; pub const NR_OF_DATA_AND_CFG_REGISTERS: usize = 14;
// This is the selected resoltion for the STM LIS3MDL device for the 4 Gauss sensitivity setting.
const FIELD_LSB_PER_GAUSS_4_SENS: f32 = 1.0 / 6842.0; // Register adresses to access various bytes from the raw reply.
pub const X_LOWBYTE_IDX: usize = 9;
pub const Y_LOWBYTE_IDX: usize = 11;
pub const Z_LOWBYTE_IDX: usize = 13;
pub trait SpiInterface { pub trait SpiInterface {
type Error; type Error;
@ -40,13 +46,53 @@ impl SpiInterface for SpiDummyInterface {
type Error = (); type Error = ();
fn transfer(&mut self, _tx: &[u8], rx: &mut [u8]) -> Result<(), Self::Error> { fn transfer(&mut self, _tx: &[u8], rx: &mut [u8]) -> Result<(), Self::Error> {
rx[0..2].copy_from_slice(&self.dummy_val_0.to_be_bytes()); rx[X_LOWBYTE_IDX..X_LOWBYTE_IDX + 2].copy_from_slice(&self.dummy_val_0.to_le_bytes());
rx[2..4].copy_from_slice(&self.dummy_val_1.to_be_bytes()); rx[Y_LOWBYTE_IDX..Y_LOWBYTE_IDX + 2].copy_from_slice(&self.dummy_val_1.to_be_bytes());
rx[4..6].copy_from_slice(&self.dummy_val_2.to_be_bytes()); rx[Z_LOWBYTE_IDX..Z_LOWBYTE_IDX + 2].copy_from_slice(&self.dummy_val_2.to_be_bytes());
Ok(()) Ok(())
} }
} }
pub struct SpiSimInterface {
pub sim_request_tx: mpsc::Sender<SimRequest>,
pub sim_reply_rx: mpsc::Receiver<SimReply>,
}
impl SpiInterface for SpiSimInterface {
type Error = ();
fn transfer(&mut self, tx: &[u8], rx: &mut [u8]) -> Result<(), Self::Error> {
let mgm_sensor_request = MgmRequestLis3Mdl::RequestSensorData;
self.sim_request_tx
.send(SimRequest::new_with_epoch_time(mgm_sensor_request))
.expect("failed to send request");
self.sim_reply_rx.recv().expect("reply timeout");
/*
let mgm_req_json = serde_json::to_string(&mgm_sensor_request)?;
self.udp_socket
.send_to(mgm_req_json.as_bytes(), self.sim_addr)
.unwrap();
*/
Ok(())
}
}
pub enum SpiSimInterfaceWrapper {
Dummy(SpiDummyInterface),
Sim(SpiSimInterface),
}
impl SpiInterface for SpiSimInterfaceWrapper {
type Error = ();
fn transfer(&mut self, tx: &[u8], rx: &mut [u8]) -> Result<(), Self::Error> {
match self {
SpiSimInterfaceWrapper::Dummy(dummy) => dummy.transfer(tx, rx),
SpiSimInterfaceWrapper::Sim(sim_if) => sim_if.transfer(tx, rx),
}
}
}
#[derive(Default, Debug, Copy, Clone, Serialize, Deserialize)] #[derive(Default, Debug, Copy, Clone, Serialize, Deserialize)]
pub struct MgmData { pub struct MgmData {
pub valid: bool, pub valid: bool,
@ -76,13 +122,13 @@ pub struct MgmHandlerLis3Mdl<ComInterface: SpiInterface, TmSender: EcssTmSender>
#[new(value = "ModeAndSubmode::new(satrs_example::DeviceMode::Off as u32, 0)")] #[new(value = "ModeAndSubmode::new(satrs_example::DeviceMode::Off as u32, 0)")]
mode_and_submode: ModeAndSubmode, mode_and_submode: ModeAndSubmode,
#[new(default)] #[new(default)]
tx_buf: [u8; 12], tx_buf: [u8; 32],
#[new(default)] #[new(default)]
rx_buf: [u8; 12], rx_buf: [u8; 32],
#[new(default)] #[new(default)]
tm_buf: [u8; 16], tm_buf: [u8; 16],
#[new(default)] #[new(default)]
stamp_helper: TimeStampHelper, stamp_helper: TimestampHelper,
} }
impl<ComInterface: SpiInterface, TmSender: EcssTmSender> MgmHandlerLis3Mdl<ComInterface, TmSender> { impl<ComInterface: SpiInterface, TmSender: EcssTmSender> MgmHandlerLis3Mdl<ComInterface, TmSender> {
@ -94,17 +140,35 @@ impl<ComInterface: SpiInterface, TmSender: EcssTmSender> MgmHandlerLis3Mdl<ComIn
if self.mode() == DeviceMode::Normal as u32 { if self.mode() == DeviceMode::Normal as u32 {
log::trace!("polling LIS3MDL sensor {}", self.dev_str); log::trace!("polling LIS3MDL sensor {}", self.dev_str);
// Communicate with the device. // Communicate with the device.
let result = self.com_interface.transfer(&self.tx_buf, &mut self.rx_buf); let result = self.com_interface.transfer(
&self.tx_buf[0..NR_OF_DATA_AND_CFG_REGISTERS + 1],
&mut self.rx_buf[0..NR_OF_DATA_AND_CFG_REGISTERS + 1],
);
assert!(result.is_ok()); assert!(result.is_ok());
// Actual data begins on the second byte, similarly to how a lot of SPI devices behave. // Actual data begins on the second byte, similarly to how a lot of SPI devices behave.
let x_raw = i16::from_be_bytes(self.rx_buf[1..3].try_into().unwrap()); let x_raw = i16::from_le_bytes(
let y_raw = i16::from_be_bytes(self.rx_buf[3..5].try_into().unwrap()); self.rx_buf[X_LOWBYTE_IDX..X_LOWBYTE_IDX + 2]
let z_raw = i16::from_be_bytes(self.rx_buf[5..7].try_into().unwrap()); .try_into()
.unwrap(),
);
let y_raw = i16::from_le_bytes(
self.rx_buf[Y_LOWBYTE_IDX..Y_LOWBYTE_IDX + 2]
.try_into()
.unwrap(),
);
let z_raw = i16::from_le_bytes(
self.rx_buf[Z_LOWBYTE_IDX..Z_LOWBYTE_IDX + 2]
.try_into()
.unwrap(),
);
// Simple scaling to retrieve the float value, assuming a sensor resolution of // Simple scaling to retrieve the float value, assuming a sensor resolution of
let mut mgm_guard = self.shared_mgm_set.lock().unwrap(); let mut mgm_guard = self.shared_mgm_set.lock().unwrap();
mgm_guard.x = x_raw as f32 * GAUSS_TO_MICROTESLA_FACTOR * FIELD_LSB_PER_GAUSS_4_SENS; mgm_guard.x =
mgm_guard.y = y_raw as f32 * GAUSS_TO_MICROTESLA_FACTOR * FIELD_LSB_PER_GAUSS_4_SENS; x_raw as f32 * GAUSS_TO_MICROTESLA_FACTOR as f32 * FIELD_LSB_PER_GAUSS_4_SENS;
mgm_guard.z = z_raw as f32 * GAUSS_TO_MICROTESLA_FACTOR * FIELD_LSB_PER_GAUSS_4_SENS; mgm_guard.y =
y_raw as f32 * GAUSS_TO_MICROTESLA_FACTOR as f32 * FIELD_LSB_PER_GAUSS_4_SENS;
mgm_guard.z =
z_raw as f32 * GAUSS_TO_MICROTESLA_FACTOR as f32 * FIELD_LSB_PER_GAUSS_4_SENS;
drop(mgm_guard); drop(mgm_guard);
} }
} }

View File

@ -224,7 +224,6 @@ pub mod pool {
pub mod tasks { pub mod tasks {
pub const FREQ_MS_UDP_TMTC: u64 = 200; pub const FREQ_MS_UDP_TMTC: u64 = 200;
pub const FREQ_MS_EVENT_HANDLING: u64 = 400;
pub const FREQ_MS_AOCS: u64 = 500; pub const FREQ_MS_AOCS: u64 = 500;
pub const FREQ_MS_PUS_STACK: u64 = 200; pub const FREQ_MS_PUS_STACK: u64 = 200;
} }

View File

@ -1,3 +1,4 @@
//! This module contains all component related to the direct interface of the example. //! This module contains all component related to the direct interface of the example.
pub mod sim_client_udp;
pub mod tcp; pub mod tcp;
pub mod udp; pub mod udp;

View File

@ -0,0 +1,172 @@
use std::{
collections::HashMap,
net::{Ipv4Addr, SocketAddr, SocketAddrV4, UdpSocket},
sync::mpsc,
time::Duration,
};
use satrs_minisim::{udp::SIM_CTRL_PORT, SimComponent, SimMessageProvider, SimReply, SimRequest};
use satrs_minisim::{SimCtrlReply, SimCtrlRequest};
struct SimReplyMap(pub HashMap<SimComponent, mpsc::Sender<SimReply>>);
pub fn create_sim_client(sim_request_rx: mpsc::Receiver<SimRequest>) -> Option<SimClientUdp> {
match SimClientUdp::new(
SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, SIM_CTRL_PORT)),
sim_request_rx,
) {
Ok(sim_client) => {
log::info!("simulator client connection success");
return Some(sim_client);
}
Err(e) => match e {
SimClientCreationResult::Io(e) => {
log::warn!("creating SIM client failed with io error {}", e);
}
SimClientCreationResult::Timeout => {
log::warn!("timeout when attempting connection to SIM client");
}
SimClientCreationResult::InvalidPingReply(reply) => {
log::warn!(
"invalid ping reply when attempting connection to SIM client: {}",
reply
);
}
},
}
None
}
#[derive(thiserror::Error, Debug)]
pub enum SimClientCreationResult {
#[error("io error: {0}")]
Io(#[from] std::io::Error),
#[error("timeout when trying to connect to sim UDP server")]
Timeout,
#[error("invalid ping reply when trying connection to UDP sim server")]
InvalidPingReply(#[from] serde_json::Error),
}
pub struct SimClientUdp {
udp_client: UdpSocket,
simulator_addr: SocketAddr,
sim_request_rx: mpsc::Receiver<SimRequest>,
reply_map: SimReplyMap,
reply_buf: [u8; 4096],
}
impl SimClientUdp {
pub fn new(
simulator_addr: SocketAddr,
sim_request_rx: mpsc::Receiver<SimRequest>,
) -> Result<Self, SimClientCreationResult> {
let mut reply_buf: [u8; 4096] = [0; 4096];
let udp_client = UdpSocket::bind("127.0.0.1:0")?;
udp_client.set_read_timeout(Some(Duration::from_millis(100)))?;
let sim_req = SimRequest::new_with_epoch_time(SimCtrlRequest::Ping);
let sim_req_json = serde_json::to_string(&sim_req).expect("failed to serialize SimRequest");
udp_client.send_to(sim_req_json.as_bytes(), simulator_addr)?;
match udp_client.recv(&mut reply_buf) {
Ok(reply_len) => {
let sim_reply: SimCtrlReply = serde_json::from_slice(&reply_buf[0..reply_len])?;
udp_client.set_read_timeout(None)?;
match sim_reply {
SimCtrlReply::Pong => Ok(Self {
udp_client,
simulator_addr,
sim_request_rx,
reply_map: SimReplyMap(HashMap::new()),
reply_buf,
}),
SimCtrlReply::InvalidRequest(_) => {
panic!("received invalid request reply from UDP sim server")
}
}
}
Err(e) => {
if e.kind() == std::io::ErrorKind::TimedOut
|| e.kind() == std::io::ErrorKind::WouldBlock
{
Err(SimClientCreationResult::Timeout)
} else {
Err(SimClientCreationResult::Io(e))
}
}
}
}
pub fn operation(&mut self) {
let mut no_sim_requests_handled = true;
let mut no_data_from_udp_server_received = true;
loop {
match self.sim_request_rx.try_recv() {
Ok(request) => {
let request_json =
serde_json::to_string(&request).expect("failed to serialize SimRequest");
if let Err(e) = self
.udp_client
.send_to(request_json.as_bytes(), self.simulator_addr)
{
log::error!("error sending data to UDP SIM server: {}", e);
break;
} else {
no_sim_requests_handled = false;
}
}
Err(e) => match e {
mpsc::TryRecvError::Empty => {
break;
}
mpsc::TryRecvError::Disconnected => {
log::warn!("SIM request sender disconnected");
break;
}
},
}
}
loop {
match self.udp_client.recv(&mut self.reply_buf) {
Ok(recvd_bytes) => {
no_data_from_udp_server_received = false;
let sim_reply_result: serde_json::Result<SimReply> =
serde_json::from_slice(&self.reply_buf[0..recvd_bytes]);
match sim_reply_result {
Ok(sim_reply) => {
if let Some(sender) = self.reply_map.0.get(&sim_reply.component()) {
sender.send(sim_reply).expect("failed to send SIM reply");
} else {
log::warn!(
"no recipient for SIM reply from component {:?}",
sim_reply.component()
);
}
}
Err(e) => {
log::warn!("failed to deserialize SIM reply: {}", e);
}
}
}
Err(e) => {
if e.kind() == std::io::ErrorKind::WouldBlock
|| e.kind() == std::io::ErrorKind::TimedOut
{
break;
}
log::error!("error receiving data from UDP SIM server: {}", e);
break;
}
}
}
if no_sim_requests_handled && no_data_from_udp_server_received {
std::thread::sleep(Duration::from_millis(5));
}
}
pub fn add_reply_recipient(
&mut self,
component: SimComponent,
reply_sender: mpsc::Sender<SimReply>,
) {
self.reply_map.0.insert(component, reply_sender);
}
}

View File

@ -9,12 +9,12 @@ pub enum DeviceMode {
Normal = 2, Normal = 2,
} }
pub struct TimeStampHelper { pub struct TimestampHelper {
stamper: CdsTime, stamper: CdsTime,
time_stamp: [u8; 7], time_stamp: [u8; 7],
} }
impl TimeStampHelper { impl TimestampHelper {
pub fn stamp(&self) -> &[u8] { pub fn stamp(&self) -> &[u8] {
&self.time_stamp &self.time_stamp
} }
@ -29,7 +29,7 @@ impl TimeStampHelper {
} }
} }
impl Default for TimeStampHelper { impl Default for TimestampHelper {
fn default() -> Self { fn default() -> Self {
Self { Self {
stamper: CdsTime::now_with_u16_days().expect("creating time stamper failed"), stamper: CdsTime::now_with_u16_days().expect("creating time stamper failed"),

View File

@ -19,12 +19,14 @@ use satrs::hal::std::udp_server::UdpTcServer;
use satrs::request::GenericMessage; use satrs::request::GenericMessage;
use satrs::tmtc::{PacketSenderWithSharedPool, SharedPacketPool}; use satrs::tmtc::{PacketSenderWithSharedPool, SharedPacketPool};
use satrs_example::config::pool::{create_sched_tc_pool, create_static_pools}; use satrs_example::config::pool::{create_sched_tc_pool, create_static_pools};
use satrs_example::config::tasks::{ use satrs_example::config::tasks::{FREQ_MS_AOCS, FREQ_MS_PUS_STACK, FREQ_MS_UDP_TMTC};
FREQ_MS_AOCS, FREQ_MS_EVENT_HANDLING, FREQ_MS_PUS_STACK, FREQ_MS_UDP_TMTC,
};
use satrs_example::config::{OBSW_SERVER_ADDR, PACKET_ID_VALIDATOR, SERVER_PORT}; use satrs_example::config::{OBSW_SERVER_ADDR, PACKET_ID_VALIDATOR, SERVER_PORT};
use crate::acs::mgm::{MgmHandlerLis3Mdl, MpscModeLeafInterface, SpiDummyInterface}; use crate::acs::mgm::{
MgmHandlerLis3Mdl, MpscModeLeafInterface, SpiDummyInterface, SpiSimInterface,
SpiSimInterfaceWrapper,
};
use crate::interface::sim_client_udp::create_sim_client;
use crate::interface::tcp::{SyncTcpTmSource, TcpTask}; use crate::interface::tcp::{SyncTcpTmSource, TcpTask};
use crate::interface::udp::{StaticUdpTmHandler, UdpTmtcServer}; use crate::interface::udp::{StaticUdpTmHandler, UdpTmtcServer};
use crate::logger::setup_logger; use crate::logger::setup_logger;
@ -60,6 +62,10 @@ fn static_tmtc_pool_main() {
let tm_sink_tx_sender = let tm_sink_tx_sender =
PacketSenderWithSharedPool::new(tm_sink_tx.clone(), shared_tm_pool_wrapper.clone()); PacketSenderWithSharedPool::new(tm_sink_tx.clone(), shared_tm_pool_wrapper.clone());
let (sim_request_tx, sim_request_rx) = mpsc::channel();
let (mgm_sim_reply_tx, mgm_sim_reply_rx) = mpsc::channel();
let mut opt_sim_client = create_sim_client(sim_request_rx);
let (mgm_handler_composite_tx, mgm_handler_composite_rx) = let (mgm_handler_composite_tx, mgm_handler_composite_rx) =
mpsc::channel::<GenericMessage<CompositeRequest>>(); mpsc::channel::<GenericMessage<CompositeRequest>>();
let (mgm_handler_mode_tx, mgm_handler_mode_rx) = mpsc::channel::<GenericMessage<ModeRequest>>(); let (mgm_handler_mode_tx, mgm_handler_mode_rx) = mpsc::channel::<GenericMessage<ModeRequest>>();
@ -197,13 +203,22 @@ fn static_tmtc_pool_main() {
let (mgm_handler_mode_reply_to_parent_tx, _mgm_handler_mode_reply_to_parent_rx) = let (mgm_handler_mode_reply_to_parent_tx, _mgm_handler_mode_reply_to_parent_rx) =
mpsc::channel(); mpsc::channel();
let dummy_spi_interface = SpiDummyInterface::default();
let shared_mgm_set = Arc::default(); let shared_mgm_set = Arc::default();
let mode_leaf_interface = MpscModeLeafInterface { let mode_leaf_interface = MpscModeLeafInterface {
request_rx: mgm_handler_mode_rx, request_rx: mgm_handler_mode_rx,
reply_tx_to_pus: pus_mode_reply_tx, reply_tx_to_pus: pus_mode_reply_tx,
reply_tx_to_parent: mgm_handler_mode_reply_to_parent_tx, reply_tx_to_parent: mgm_handler_mode_reply_to_parent_tx,
}; };
let mgm_spi_interface = if let Some(sim_client) = opt_sim_client.as_mut() {
sim_client.add_reply_recipient(satrs_minisim::SimComponent::MgmLis3Mdl, mgm_sim_reply_tx);
SpiSimInterfaceWrapper::Sim(SpiSimInterface {
sim_request_tx: sim_request_tx.clone(),
sim_reply_rx: mgm_sim_reply_rx,
})
} else {
SpiSimInterfaceWrapper::Dummy(SpiDummyInterface::default())
};
let mut mgm_handler = MgmHandlerLis3Mdl::new( let mut mgm_handler = MgmHandlerLis3Mdl::new(
MGM_HANDLER_0, MGM_HANDLER_0,
"MGM_0", "MGM_0",
@ -211,7 +226,7 @@ fn static_tmtc_pool_main() {
mgm_handler_composite_rx, mgm_handler_composite_rx,
pus_hk_reply_tx, pus_hk_reply_tx,
tm_sink_tx, tm_sink_tx,
dummy_spi_interface, mgm_spi_interface,
shared_mgm_set, shared_mgm_set,
); );
@ -247,14 +262,18 @@ fn static_tmtc_pool_main() {
}) })
.unwrap(); .unwrap();
info!("Starting event handling task"); let mut opt_jh_sim_client = None;
let jh_event_handling = thread::Builder::new() if let Some(mut sim_client) = opt_sim_client {
.name("sat-rs events".to_string()) info!("Starting UDP sim client task");
opt_jh_sim_client = Some(
thread::Builder::new()
.name("sat-rs sim adapter".to_string())
.spawn(move || loop { .spawn(move || loop {
event_handler.periodic_operation(); sim_client.operation();
thread::sleep(Duration::from_millis(FREQ_MS_EVENT_HANDLING));
}) })
.unwrap(); .unwrap(),
);
}
info!("Starting AOCS thread"); info!("Starting AOCS thread");
let jh_aocs = thread::Builder::new() let jh_aocs = thread::Builder::new()
@ -269,6 +288,7 @@ fn static_tmtc_pool_main() {
let jh_pus_handler = thread::Builder::new() let jh_pus_handler = thread::Builder::new()
.name("sat-rs pus".to_string()) .name("sat-rs pus".to_string())
.spawn(move || loop { .spawn(move || loop {
event_handler.periodic_operation();
pus_stack.periodic_operation(); pus_stack.periodic_operation();
thread::sleep(Duration::from_millis(FREQ_MS_PUS_STACK)); thread::sleep(Duration::from_millis(FREQ_MS_PUS_STACK));
}) })
@ -283,9 +303,11 @@ fn static_tmtc_pool_main() {
jh_tm_funnel jh_tm_funnel
.join() .join()
.expect("Joining TM Funnel thread failed"); .expect("Joining TM Funnel thread failed");
jh_event_handling if let Some(jh_sim_client) = opt_jh_sim_client {
jh_sim_client
.join() .join()
.expect("Joining Event Manager thread failed"); .expect("Joining SIM client thread failed");
}
jh_aocs.join().expect("Joining AOCS thread failed"); jh_aocs.join().expect("Joining AOCS thread failed");
jh_pus_handler jh_pus_handler
.join() .join()
@ -298,6 +320,10 @@ fn dyn_tmtc_pool_main() {
let (tm_funnel_tx, tm_funnel_rx) = mpsc::channel(); let (tm_funnel_tx, tm_funnel_rx) = mpsc::channel();
let (tm_server_tx, tm_server_rx) = mpsc::channel(); let (tm_server_tx, tm_server_rx) = mpsc::channel();
let (sim_request_tx, sim_request_rx) = mpsc::channel();
let (mgm_sim_reply_tx, mgm_sim_reply_rx) = mpsc::channel();
let mut opt_sim_client = create_sim_client(sim_request_rx);
// Some request are targetable. This map is used to retrieve sender handles based on a target ID. // Some request are targetable. This map is used to retrieve sender handles based on a target ID.
let (mgm_handler_composite_tx, mgm_handler_composite_rx) = let (mgm_handler_composite_tx, mgm_handler_composite_rx) =
mpsc::channel::<GenericMessage<CompositeRequest>>(); mpsc::channel::<GenericMessage<CompositeRequest>>();
@ -414,13 +440,22 @@ fn dyn_tmtc_pool_main() {
let (mgm_handler_mode_reply_to_parent_tx, _mgm_handler_mode_reply_to_parent_rx) = let (mgm_handler_mode_reply_to_parent_tx, _mgm_handler_mode_reply_to_parent_rx) =
mpsc::channel(); mpsc::channel();
let dummy_spi_interface = SpiDummyInterface::default();
let shared_mgm_set = Arc::default(); let shared_mgm_set = Arc::default();
let mode_leaf_interface = MpscModeLeafInterface { let mode_leaf_interface = MpscModeLeafInterface {
request_rx: mgm_handler_mode_rx, request_rx: mgm_handler_mode_rx,
reply_tx_to_pus: pus_mode_reply_tx, reply_tx_to_pus: pus_mode_reply_tx,
reply_tx_to_parent: mgm_handler_mode_reply_to_parent_tx, reply_tx_to_parent: mgm_handler_mode_reply_to_parent_tx,
}; };
let mgm_spi_interface = if let Some(sim_client) = opt_sim_client.as_mut() {
sim_client.add_reply_recipient(satrs_minisim::SimComponent::MgmLis3Mdl, mgm_sim_reply_tx);
SpiSimInterfaceWrapper::Sim(SpiSimInterface {
sim_request_tx: sim_request_tx.clone(),
sim_reply_rx: mgm_sim_reply_rx,
})
} else {
SpiSimInterfaceWrapper::Dummy(SpiDummyInterface::default())
};
let mut mgm_handler = MgmHandlerLis3Mdl::new( let mut mgm_handler = MgmHandlerLis3Mdl::new(
MGM_HANDLER_0, MGM_HANDLER_0,
"MGM_0", "MGM_0",
@ -428,7 +463,7 @@ fn dyn_tmtc_pool_main() {
mgm_handler_composite_rx, mgm_handler_composite_rx,
pus_hk_reply_tx, pus_hk_reply_tx,
tm_funnel_tx, tm_funnel_tx,
dummy_spi_interface, mgm_spi_interface,
shared_mgm_set, shared_mgm_set,
); );
@ -464,14 +499,18 @@ fn dyn_tmtc_pool_main() {
}) })
.unwrap(); .unwrap();
info!("Starting event handling task"); let mut opt_jh_sim_client = None;
let jh_event_handling = thread::Builder::new() if let Some(mut sim_client) = opt_sim_client {
.name("sat-rs events".to_string()) info!("Starting UDP sim client task");
opt_jh_sim_client = Some(
thread::Builder::new()
.name("sat-rs sim adapter".to_string())
.spawn(move || loop { .spawn(move || loop {
event_handler.periodic_operation(); sim_client.operation();
thread::sleep(Duration::from_millis(FREQ_MS_EVENT_HANDLING));
}) })
.unwrap(); .unwrap(),
);
}
info!("Starting AOCS thread"); info!("Starting AOCS thread");
let jh_aocs = thread::Builder::new() let jh_aocs = thread::Builder::new()
@ -487,6 +526,7 @@ fn dyn_tmtc_pool_main() {
.name("sat-rs pus".to_string()) .name("sat-rs pus".to_string())
.spawn(move || loop { .spawn(move || loop {
pus_stack.periodic_operation(); pus_stack.periodic_operation();
event_handler.periodic_operation();
thread::sleep(Duration::from_millis(FREQ_MS_PUS_STACK)); thread::sleep(Duration::from_millis(FREQ_MS_PUS_STACK));
}) })
.unwrap(); .unwrap();
@ -500,9 +540,11 @@ fn dyn_tmtc_pool_main() {
jh_tm_funnel jh_tm_funnel
.join() .join()
.expect("Joining TM Funnel thread failed"); .expect("Joining TM Funnel thread failed");
jh_event_handling if let Some(jh_sim_client) = opt_jh_sim_client {
jh_sim_client
.join() .join()
.expect("Joining Event Manager thread failed"); .expect("Joining SIM client thread failed");
}
jh_aocs.join().expect("Joining AOCS thread failed"); jh_aocs.join().expect("Joining AOCS thread failed");
jh_pus_handler jh_pus_handler
.join() .join()

View File

@ -19,7 +19,7 @@ use satrs::tmtc::{PacketAsVec, PacketInPool};
use satrs::ComponentId; use satrs::ComponentId;
use satrs_example::config::components::PUS_ROUTING_SERVICE; use satrs_example::config::components::PUS_ROUTING_SERVICE;
use satrs_example::config::{tmtc_err, CustomPusServiceId}; use satrs_example::config::{tmtc_err, CustomPusServiceId};
use satrs_example::TimeStampHelper; use satrs_example::TimestampHelper;
use std::fmt::Debug; use std::fmt::Debug;
use std::sync::mpsc::{self, Sender}; use std::sync::mpsc::{self, Sender};
@ -53,7 +53,7 @@ pub struct PusTcDistributor<TmSender: EcssTmSender> {
pub tm_sender: TmSender, pub tm_sender: TmSender,
pub verif_reporter: VerificationReporter, pub verif_reporter: VerificationReporter,
pub pus_router: PusTcMpscRouter, pub pus_router: PusTcMpscRouter,
stamp_helper: TimeStampHelper, stamp_helper: TimestampHelper,
} }
impl<TmSender: EcssTmSender> PusTcDistributor<TmSender> { impl<TmSender: EcssTmSender> PusTcDistributor<TmSender> {
@ -66,7 +66,7 @@ impl<TmSender: EcssTmSender> PusTcDistributor<TmSender> {
PUS_ROUTING_SERVICE.apid, PUS_ROUTING_SERVICE.apid,
), ),
pus_router, pus_router,
stamp_helper: TimeStampHelper::default(), stamp_helper: TimestampHelper::default(),
} }
} }

View File

@ -6,14 +6,17 @@ use asynchronix::{
}; };
use satrs::power::SwitchStateBinary; use satrs::power::SwitchStateBinary;
use satrs_minisim::{ use satrs_minisim::{
acs::{MgmReply, MgmSensorValues, MgtDipole, MgtHkSet, MgtReply, MGT_GEN_MAGNETIC_FIELD}, acs::{
lis3mdl::MgmLis3MdlReply, MgmReplyCommon, MgmReplyProvider, MgmSensorValuesMicroTesla,
MgtDipole, MgtHkSet, MgtReply, MGT_GEN_MAGNETIC_FIELD,
},
SimReply, SimReply,
}; };
use crate::time::current_millis; use crate::time::current_millis;
// Earth magnetic field varies between -30 uT and 30 uT // Earth magnetic field varies between -30 uT and 30 uT
const AMPLITUDE_MGM: f32 = 0.03; const AMPLITUDE_MGM_UT: f32 = 30.0;
// Lets start with a simple frequency here. // Lets start with a simple frequency here.
const FREQUENCY_MGM: f32 = 1.0; const FREQUENCY_MGM: f32 = 1.0;
const PHASE_X: f32 = 0.0; const PHASE_X: f32 = 0.0;
@ -31,30 +34,34 @@ const PHASE_Z: f32 = 0.2;
/// 2. It would sample the magnetic field at a high fixed rate. This might not be possible for /// 2. It would sample the magnetic field at a high fixed rate. This might not be possible for
/// a general purpose OS, but self self-sampling at a relatively high rate (20-40 ms) might /// a general purpose OS, but self self-sampling at a relatively high rate (20-40 ms) might
/// stil lbe possible. /// stil lbe possible.
pub struct MagnetometerModel { pub struct MagnetometerModel<ReplyProvider: MgmReplyProvider> {
pub switch_state: SwitchStateBinary, pub switch_state: SwitchStateBinary,
pub periodicity: Duration, pub periodicity: Duration,
pub external_mag_field: Option<MgmSensorValues>, pub external_mag_field: Option<MgmSensorValuesMicroTesla>,
pub reply_sender: mpsc::Sender<SimReply>, pub reply_sender: mpsc::Sender<SimReply>,
pub phatom: std::marker::PhantomData<ReplyProvider>,
} }
impl MagnetometerModel { impl MagnetometerModel<MgmLis3MdlReply> {
pub fn new(periodicity: Duration, reply_sender: mpsc::Sender<SimReply>) -> Self { pub fn new_for_lis3mdl(periodicity: Duration, reply_sender: mpsc::Sender<SimReply>) -> Self {
Self { Self {
switch_state: SwitchStateBinary::Off, switch_state: SwitchStateBinary::Off,
periodicity, periodicity,
external_mag_field: None, external_mag_field: None,
reply_sender, reply_sender,
phatom: std::marker::PhantomData,
}
} }
} }
impl<ReplyProvider: MgmReplyProvider> MagnetometerModel<ReplyProvider> {
pub async fn switch_device(&mut self, switch_state: SwitchStateBinary) { pub async fn switch_device(&mut self, switch_state: SwitchStateBinary) {
self.switch_state = switch_state; self.switch_state = switch_state;
} }
pub async fn send_sensor_values(&mut self, _: (), scheduler: &Scheduler<Self>) { pub async fn send_sensor_values(&mut self, _: (), scheduler: &Scheduler<Self>) {
self.reply_sender self.reply_sender
.send(SimReply::new(MgmReply { .send(ReplyProvider::create_mgm_reply(MgmReplyCommon {
switch_state: self.switch_state, switch_state: self.switch_state,
sensor_values: self.calculate_current_mgm_tuple(current_millis(scheduler.time())), sensor_values: self.calculate_current_mgm_tuple(current_millis(scheduler.time())),
})) }))
@ -63,23 +70,23 @@ impl MagnetometerModel {
// Devices like magnetorquers generate a strong magnetic field which overrides the default // Devices like magnetorquers generate a strong magnetic field which overrides the default
// model for the measured magnetic field. // model for the measured magnetic field.
pub async fn apply_external_magnetic_field(&mut self, field: MgmSensorValues) { pub async fn apply_external_magnetic_field(&mut self, field: MgmSensorValuesMicroTesla) {
self.external_mag_field = Some(field); self.external_mag_field = Some(field);
} }
fn calculate_current_mgm_tuple(&self, time_ms: u64) -> MgmSensorValues { fn calculate_current_mgm_tuple(&self, time_ms: u64) -> MgmSensorValuesMicroTesla {
if SwitchStateBinary::On == self.switch_state { if SwitchStateBinary::On == self.switch_state {
if let Some(ext_field) = self.external_mag_field { if let Some(ext_field) = self.external_mag_field {
return ext_field; return ext_field;
} }
let base_sin_val = 2.0 * PI * FREQUENCY_MGM * (time_ms as f32 / 1000.0); let base_sin_val = 2.0 * PI * FREQUENCY_MGM * (time_ms as f32 / 1000.0);
return MgmSensorValues { return MgmSensorValuesMicroTesla {
x: AMPLITUDE_MGM * (base_sin_val + PHASE_X).sin(), x: AMPLITUDE_MGM_UT * (base_sin_val + PHASE_X).sin(),
y: AMPLITUDE_MGM * (base_sin_val + PHASE_Y).sin(), y: AMPLITUDE_MGM_UT * (base_sin_val + PHASE_Y).sin(),
z: AMPLITUDE_MGM * (base_sin_val + PHASE_Z).sin(), z: AMPLITUDE_MGM_UT * (base_sin_val + PHASE_Z).sin(),
}; };
} }
MgmSensorValues { MgmSensorValuesMicroTesla {
x: 0.0, x: 0.0,
y: 0.0, y: 0.0,
z: 0.0, z: 0.0,
@ -87,13 +94,13 @@ impl MagnetometerModel {
} }
} }
impl Model for MagnetometerModel {} impl<ReplyProvider: MgmReplyProvider> Model for MagnetometerModel<ReplyProvider> {}
pub struct MagnetorquerModel { pub struct MagnetorquerModel {
switch_state: SwitchStateBinary, switch_state: SwitchStateBinary,
torquing: bool, torquing: bool,
torque_dipole: MgtDipole, torque_dipole: MgtDipole,
pub gen_magnetic_field: Output<MgmSensorValues>, pub gen_magnetic_field: Output<MgmSensorValuesMicroTesla>,
reply_sender: mpsc::Sender<SimReply>, reply_sender: mpsc::Sender<SimReply>,
} }
@ -153,7 +160,7 @@ impl MagnetorquerModel {
.unwrap(); .unwrap();
} }
fn calc_magnetic_field(&self, _: MgtDipole) -> MgmSensorValues { fn calc_magnetic_field(&self, _: MgtDipole) -> MgmSensorValuesMicroTesla {
// Simplified model: Just returns some fixed magnetic field for now. // Simplified model: Just returns some fixed magnetic field for now.
// Later, we could make this more fancy by incorporating the commanded dipole. // Later, we could make this more fancy by incorporating the commanded dipole.
MGT_GEN_MAGNETIC_FIELD MGT_GEN_MAGNETIC_FIELD
@ -179,7 +186,10 @@ pub mod tests {
use satrs::power::SwitchStateBinary; use satrs::power::SwitchStateBinary;
use satrs_minisim::{ use satrs_minisim::{
acs::{MgmReply, MgmRequest, MgtDipole, MgtHkSet, MgtReply, MgtRequest}, acs::{
lis3mdl::{self, MgmLis3MdlReply},
MgmRequest, MgtDipole, MgtHkSet, MgtReply, MgtRequest,
},
eps::PcduSwitch, eps::PcduSwitch,
SerializableSimMsgPayload, SimMessageProvider, SimRequest, SimTarget, SerializableSimMsgPayload, SimMessageProvider, SimRequest, SimTarget,
}; };
@ -198,13 +208,13 @@ pub mod tests {
let sim_reply = sim_testbench.try_receive_next_reply(); let sim_reply = sim_testbench.try_receive_next_reply();
assert!(sim_reply.is_some()); assert!(sim_reply.is_some());
let sim_reply = sim_reply.unwrap(); let sim_reply = sim_reply.unwrap();
assert_eq!(sim_reply.target(), SimTarget::Mgm); assert_eq!(sim_reply.component(), SimTarget::MgmLis3Mdl);
let reply = MgmReply::from_sim_message(&sim_reply) let reply = MgmLis3MdlReply::from_sim_message(&sim_reply)
.expect("failed to deserialize MGM sensor values"); .expect("failed to deserialize MGM sensor values");
assert_eq!(reply.switch_state, SwitchStateBinary::Off); assert_eq!(reply.common.switch_state, SwitchStateBinary::Off);
assert_eq!(reply.sensor_values.x, 0.0); assert_eq!(reply.common.sensor_values.x, 0.0);
assert_eq!(reply.sensor_values.y, 0.0); assert_eq!(reply.common.sensor_values.y, 0.0);
assert_eq!(reply.sensor_values.z, 0.0); assert_eq!(reply.common.sensor_values.z, 0.0);
} }
#[test] #[test]
@ -221,8 +231,8 @@ pub mod tests {
let mut sim_reply_res = sim_testbench.try_receive_next_reply(); let mut sim_reply_res = sim_testbench.try_receive_next_reply();
assert!(sim_reply_res.is_some()); assert!(sim_reply_res.is_some());
let mut sim_reply = sim_reply_res.unwrap(); let mut sim_reply = sim_reply_res.unwrap();
assert_eq!(sim_reply.target(), SimTarget::Mgm); assert_eq!(sim_reply.component(), SimTarget::MgmLis3Mdl);
let first_reply = MgmReply::from_sim_message(&sim_reply) let first_reply = MgmLis3MdlReply::from_sim_message(&sim_reply)
.expect("failed to deserialize MGM sensor values"); .expect("failed to deserialize MGM sensor values");
sim_testbench.step_by(Duration::from_millis(50)); sim_testbench.step_by(Duration::from_millis(50));
@ -236,8 +246,24 @@ pub mod tests {
assert!(sim_reply_res.is_some()); assert!(sim_reply_res.is_some());
sim_reply = sim_reply_res.unwrap(); sim_reply = sim_reply_res.unwrap();
let second_reply = MgmReply::from_sim_message(&sim_reply) let second_reply = MgmLis3MdlReply::from_sim_message(&sim_reply)
.expect("failed to deserialize MGM sensor values"); .expect("failed to deserialize MGM sensor values");
let x_conv_back = second_reply.raw.x as f32
* lis3mdl::FIELD_LSB_PER_GAUSS_4_SENS
* lis3mdl::GAUSS_TO_MICROTESLA_FACTOR as f32;
let y_conv_back = second_reply.raw.y as f32
* lis3mdl::FIELD_LSB_PER_GAUSS_4_SENS
* lis3mdl::GAUSS_TO_MICROTESLA_FACTOR as f32;
let z_conv_back = second_reply.raw.z as f32
* lis3mdl::FIELD_LSB_PER_GAUSS_4_SENS
* lis3mdl::GAUSS_TO_MICROTESLA_FACTOR as f32;
let diff_x = (second_reply.common.sensor_values.x - x_conv_back).abs();
assert!(diff_x < 0.01, "diff x too large: {}", diff_x);
let diff_y = (second_reply.common.sensor_values.y - y_conv_back).abs();
assert!(diff_y < 0.01, "diff y too large: {}", diff_y);
let diff_z = (second_reply.common.sensor_values.z - z_conv_back).abs();
assert!(diff_z < 0.01, "diff z too large: {}", diff_z);
// assert_eq!(second_reply.raw_reply, SwitchStateBinary::On);
// Check that the values are changing. // Check that the values are changing.
assert!(first_reply != second_reply); assert!(first_reply != second_reply);
} }

View File

@ -5,10 +5,10 @@ use asynchronix::{
time::{Clock, MonotonicTime, SystemClock}, time::{Clock, MonotonicTime, SystemClock},
}; };
use satrs_minisim::{ use satrs_minisim::{
acs::{MgmRequest, MgtRequest}, acs::{lis3mdl::MgmLis3MdlReply, MgmRequestLis3Mdl, MgtRequest},
eps::PcduRequest, eps::PcduRequest,
SerializableSimMsgPayload, SimCtrlReply, SimCtrlRequest, SimMessageProvider, SimReply, SerializableSimMsgPayload, SimComponent, SimCtrlReply, SimCtrlRequest, SimMessageProvider,
SimRequest, SimRequestError, SimTarget, SimReply, SimRequest, SimRequestError,
}; };
use crate::{ use crate::{
@ -22,7 +22,7 @@ pub struct SimController {
pub request_receiver: mpsc::Receiver<SimRequest>, pub request_receiver: mpsc::Receiver<SimRequest>,
pub reply_sender: mpsc::Sender<SimReply>, pub reply_sender: mpsc::Sender<SimReply>,
pub simulation: Simulation, pub simulation: Simulation,
pub mgm_addr: Address<MagnetometerModel>, pub mgm_addr: Address<MagnetometerModel<MgmLis3MdlReply>>,
pub pcdu_addr: Address<PcduModel>, pub pcdu_addr: Address<PcduModel>,
pub mgt_addr: Address<MagnetorquerModel>, pub mgt_addr: Address<MagnetorquerModel>,
} }
@ -33,7 +33,7 @@ impl SimController {
request_receiver: mpsc::Receiver<SimRequest>, request_receiver: mpsc::Receiver<SimRequest>,
reply_sender: mpsc::Sender<SimReply>, reply_sender: mpsc::Sender<SimReply>,
simulation: Simulation, simulation: Simulation,
mgm_addr: Address<MagnetometerModel>, mgm_addr: Address<MagnetometerModel<MgmLis3MdlReply>>,
pcdu_addr: Address<PcduModel>, pcdu_addr: Address<PcduModel>,
mgt_addr: Address<MagnetorquerModel>, mgt_addr: Address<MagnetorquerModel>,
) -> Self { ) -> Self {
@ -70,11 +70,11 @@ impl SimController {
if request.timestamp < old_timestamp { if request.timestamp < old_timestamp {
log::warn!("stale data with timestamp {:?} received", request.timestamp); log::warn!("stale data with timestamp {:?} received", request.timestamp);
} }
if let Err(e) = match request.target() { if let Err(e) = match request.component() {
SimTarget::SimCtrl => self.handle_ctrl_request(&request), SimComponent::SimCtrl => self.handle_ctrl_request(&request),
SimTarget::Mgm => self.handle_mgm_request(&request), SimComponent::MgmLis3Mdl => self.handle_mgm_request(&request),
SimTarget::Mgt => self.handle_mgt_request(&request), SimComponent::Mgt => self.handle_mgt_request(&request),
SimTarget::Pcdu => self.handle_pcdu_request(&request), SimComponent::Pcdu => self.handle_pcdu_request(&request),
} { } {
self.handle_invalid_request_with_valid_target(e, &request) self.handle_invalid_request_with_valid_target(e, &request)
} }
@ -100,10 +100,11 @@ impl SimController {
} }
Ok(()) Ok(())
} }
fn handle_mgm_request(&mut self, request: &SimRequest) -> Result<(), SimRequestError> { fn handle_mgm_request(&mut self, request: &SimRequest) -> Result<(), SimRequestError> {
let mgm_request = MgmRequest::from_sim_message(request)?; let mgm_request = MgmRequestLis3Mdl::from_sim_message(request)?;
match mgm_request { match mgm_request {
MgmRequest::RequestSensorData => { MgmRequestLis3Mdl::RequestSensorData => {
self.simulation.send_event( self.simulation.send_event(
MagnetometerModel::send_sensor_values, MagnetometerModel::send_sensor_values,
(), (),
@ -156,7 +157,7 @@ impl SimController {
) { ) {
log::warn!( log::warn!(
"received invalid {:?} request: {:?}", "received invalid {:?} request: {:?}",
request.target(), request.component(),
error error
); );
self.reply_sender self.reply_sender
@ -183,7 +184,7 @@ mod tests {
let sim_reply = sim_testbench.try_receive_next_reply(); let sim_reply = sim_testbench.try_receive_next_reply();
assert!(sim_reply.is_some()); assert!(sim_reply.is_some());
let sim_reply = sim_reply.unwrap(); let sim_reply = sim_reply.unwrap();
assert_eq!(sim_reply.target(), SimTarget::SimCtrl); assert_eq!(sim_reply.component(), SimComponent::SimCtrl);
let reply = SimCtrlReply::from_sim_message(&sim_reply) let reply = SimCtrlReply::from_sim_message(&sim_reply)
.expect("failed to deserialize MGM sensor values"); .expect("failed to deserialize MGM sensor values");
assert_eq!(reply, SimCtrlReply::Pong); assert_eq!(reply, SimCtrlReply::Pong);

View File

@ -76,7 +76,7 @@ pub(crate) mod tests {
use std::time::Duration; use std::time::Duration;
use satrs_minisim::{ use satrs_minisim::{
eps::PcduRequest, SerializableSimMsgPayload, SimMessageProvider, SimRequest, SimTarget, eps::PcduRequest, SerializableSimMsgPayload, SimComponent, SimMessageProvider, SimRequest,
}; };
use crate::test_helpers::SimTestbench; use crate::test_helpers::SimTestbench;
@ -122,7 +122,7 @@ pub(crate) mod tests {
let sim_reply = sim_testbench.try_receive_next_reply(); let sim_reply = sim_testbench.try_receive_next_reply();
assert!(sim_reply.is_some()); assert!(sim_reply.is_some());
let sim_reply = sim_reply.unwrap(); let sim_reply = sim_reply.unwrap();
assert_eq!(sim_reply.target(), SimTarget::Pcdu); assert_eq!(sim_reply.component(), SimComponent::Pcdu);
let pcdu_reply = PcduReply::from_sim_message(&sim_reply) let pcdu_reply = PcduReply::from_sim_message(&sim_reply)
.expect("failed to deserialize PCDU switch info"); .expect("failed to deserialize PCDU switch info");
match pcdu_reply { match pcdu_reply {
@ -157,7 +157,7 @@ pub(crate) mod tests {
let sim_reply = sim_testbench.try_receive_next_reply(); let sim_reply = sim_testbench.try_receive_next_reply();
assert!(sim_reply.is_some()); assert!(sim_reply.is_some());
let sim_reply = sim_reply.unwrap(); let sim_reply = sim_reply.unwrap();
assert_eq!(sim_reply.target(), SimTarget::Pcdu); assert_eq!(sim_reply.component(), SimComponent::Pcdu);
let pcdu_reply = PcduReply::from_sim_message(&sim_reply) let pcdu_reply = PcduReply::from_sim_message(&sim_reply)
.expect("failed to deserialize PCDU switch info"); .expect("failed to deserialize PCDU switch info");
match pcdu_reply { match pcdu_reply {

View File

@ -1,19 +1,17 @@
use asynchronix::time::MonotonicTime; use asynchronix::time::MonotonicTime;
use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde::{de::DeserializeOwned, Deserialize, Serialize};
pub const SIM_CTRL_UDP_PORT: u16 = 7303; #[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
pub enum SimComponent {
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum SimTarget {
SimCtrl, SimCtrl,
Mgm, MgmLis3Mdl,
Mgt, Mgt,
Pcdu, Pcdu,
} }
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SimMessage { pub struct SimMessage {
pub target: SimTarget, pub target: SimComponent,
pub payload: String, pub payload: String,
} }
@ -37,10 +35,10 @@ pub enum SimMessageType {
pub trait SerializableSimMsgPayload<P: SimMessageProvider>: pub trait SerializableSimMsgPayload<P: SimMessageProvider>:
Serialize + DeserializeOwned + Sized Serialize + DeserializeOwned + Sized
{ {
const TARGET: SimTarget; const TARGET: SimComponent;
fn from_sim_message(sim_message: &P) -> Result<Self, SimMessageError<P>> { fn from_sim_message(sim_message: &P) -> Result<Self, SimMessageError<P>> {
if sim_message.target() == Self::TARGET { if sim_message.component() == Self::TARGET {
return Ok(serde_json::from_str(sim_message.payload())?); return Ok(serde_json::from_str(sim_message.payload())?);
} }
Err(SimMessageError::TargetRequestMissmatch(sim_message.clone())) Err(SimMessageError::TargetRequestMissmatch(sim_message.clone()))
@ -49,7 +47,7 @@ pub trait SerializableSimMsgPayload<P: SimMessageProvider>:
pub trait SimMessageProvider: Serialize + DeserializeOwned + Clone + Sized { pub trait SimMessageProvider: Serialize + DeserializeOwned + Clone + Sized {
fn msg_type(&self) -> SimMessageType; fn msg_type(&self) -> SimMessageType;
fn target(&self) -> SimTarget; fn component(&self) -> SimComponent;
fn payload(&self) -> &String; fn payload(&self) -> &String;
fn from_raw_data(data: &[u8]) -> serde_json::Result<Self> { fn from_raw_data(data: &[u8]) -> serde_json::Result<Self> {
serde_json::from_slice(data) serde_json::from_slice(data)
@ -78,7 +76,7 @@ impl SimRequest {
} }
impl SimMessageProvider for SimRequest { impl SimMessageProvider for SimRequest {
fn target(&self) -> SimTarget { fn component(&self) -> SimComponent {
self.inner.target self.inner.target
} }
fn payload(&self) -> &String { fn payload(&self) -> &String {
@ -109,7 +107,7 @@ impl SimReply {
} }
impl SimMessageProvider for SimReply { impl SimMessageProvider for SimReply {
fn target(&self) -> SimTarget { fn component(&self) -> SimComponent {
self.inner.target self.inner.target
} }
fn payload(&self) -> &String { fn payload(&self) -> &String {
@ -126,7 +124,7 @@ pub enum SimCtrlRequest {
} }
impl SerializableSimMsgPayload<SimRequest> for SimCtrlRequest { impl SerializableSimMsgPayload<SimRequest> for SimCtrlRequest {
const TARGET: SimTarget = SimTarget::SimCtrl; const TARGET: SimComponent = SimComponent::SimCtrl;
} }
pub type SimReplyError = SimMessageError<SimReply>; pub type SimReplyError = SimMessageError<SimReply>;
@ -151,7 +149,7 @@ pub enum SimCtrlReply {
} }
impl SerializableSimMsgPayload<SimReply> for SimCtrlReply { impl SerializableSimMsgPayload<SimReply> for SimCtrlReply {
const TARGET: SimTarget = SimTarget::SimCtrl; const TARGET: SimComponent = SimComponent::SimCtrl;
} }
impl From<SimRequestError> for SimCtrlReply { impl From<SimRequestError> for SimCtrlReply {
@ -184,7 +182,7 @@ pub mod eps {
} }
impl SerializableSimMsgPayload<SimRequest> for PcduRequest { impl SerializableSimMsgPayload<SimRequest> for PcduRequest {
const TARGET: SimTarget = SimTarget::Pcdu; const TARGET: SimComponent = SimComponent::Pcdu;
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
@ -193,7 +191,7 @@ pub mod eps {
} }
impl SerializableSimMsgPayload<SimReply> for PcduReply { impl SerializableSimMsgPayload<SimReply> for PcduReply {
const TARGET: SimTarget = SimTarget::Pcdu; const TARGET: SimComponent = SimComponent::Pcdu;
} }
} }
@ -204,41 +202,104 @@ pub mod acs {
use super::*; use super::*;
pub trait MgmReplyProvider: Send + 'static {
fn create_mgm_reply(common: MgmReplyCommon) -> SimReply;
}
#[derive(Debug, Copy, Clone, Serialize, Deserialize)] #[derive(Debug, Copy, Clone, Serialize, Deserialize)]
pub enum MgmRequest { pub enum MgmRequestLis3Mdl {
RequestSensorData, RequestSensorData,
} }
impl SerializableSimMsgPayload<SimRequest> for MgmRequest { impl SerializableSimMsgPayload<SimRequest> for MgmRequestLis3Mdl {
const TARGET: SimTarget = SimTarget::Mgm; const TARGET: SimComponent = SimComponent::MgmLis3Mdl;
} }
// Normally, small magnetometers generate their output as a signed 16 bit raw format or something // Normally, small magnetometers generate their output as a signed 16 bit raw format or something
// similar which needs to be converted to a signed float value with physical units. We will // similar which needs to be converted to a signed float value with physical units. We will
// simplify this now and generate the signed float values directly. // simplify this now and generate the signed float values directly. The unit is micro tesla.
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
pub struct MgmSensorValues { pub struct MgmSensorValuesMicroTesla {
pub x: f32, pub x: f32,
pub y: f32, pub y: f32,
pub z: f32, pub z: f32,
} }
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
pub struct MgmReply { pub struct MgmReplyCommon {
pub switch_state: SwitchStateBinary, pub switch_state: SwitchStateBinary,
pub sensor_values: MgmSensorValues, pub sensor_values: MgmSensorValuesMicroTesla,
} }
impl SerializableSimMsgPayload<SimReply> for MgmReply { pub const MGT_GEN_MAGNETIC_FIELD: MgmSensorValuesMicroTesla = MgmSensorValuesMicroTesla {
const TARGET: SimTarget = SimTarget::Mgm; x: 30.0,
} y: -30.0,
z: 30.0,
pub const MGT_GEN_MAGNETIC_FIELD: MgmSensorValues = MgmSensorValues {
x: 0.03,
y: -0.03,
z: 0.03,
}; };
pub mod lis3mdl {
use super::*;
// Field data register scaling
pub const GAUSS_TO_MICROTESLA_FACTOR: u32 = 100;
pub const FIELD_LSB_PER_GAUSS_4_SENS: f32 = 1.0 / 6842.0;
pub const FIELD_LSB_PER_GAUSS_8_SENS: f32 = 1.0 / 3421.0;
pub const FIELD_LSB_PER_GAUSS_12_SENS: f32 = 1.0 / 2281.0;
pub const FIELD_LSB_PER_GAUSS_16_SENS: f32 = 1.0 / 1711.0;
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
pub struct MgmLis3RawValues {
pub x: i16,
pub y: i16,
pub z: i16,
}
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
pub struct MgmLis3MdlReply {
pub common: MgmReplyCommon,
// Raw sensor values which are transmitted by the LIS3 device in little-endian
// order.
pub raw: MgmLis3RawValues,
}
impl MgmLis3MdlReply {
pub fn new(common: MgmReplyCommon) -> Self {
let mut raw_reply: [u8; 7] = [0; 7];
let raw_x: i16 = (common.sensor_values.x
/ (GAUSS_TO_MICROTESLA_FACTOR as f32 * FIELD_LSB_PER_GAUSS_4_SENS))
.round() as i16;
let raw_y: i16 = (common.sensor_values.y
/ (GAUSS_TO_MICROTESLA_FACTOR as f32 * FIELD_LSB_PER_GAUSS_4_SENS))
.round() as i16;
let raw_z: i16 = (common.sensor_values.z
/ (GAUSS_TO_MICROTESLA_FACTOR as f32 * FIELD_LSB_PER_GAUSS_4_SENS))
.round() as i16;
// The first byte is a dummy byte.
raw_reply[1..3].copy_from_slice(&raw_x.to_be_bytes());
raw_reply[3..5].copy_from_slice(&raw_y.to_be_bytes());
raw_reply[5..7].copy_from_slice(&raw_z.to_be_bytes());
Self {
common,
raw: MgmLis3RawValues {
x: raw_x,
y: raw_y,
z: raw_z,
},
}
}
}
impl SerializableSimMsgPayload<SimReply> for MgmLis3MdlReply {
const TARGET: SimComponent = SimComponent::MgmLis3Mdl;
}
impl MgmReplyProvider for MgmLis3MdlReply {
fn create_mgm_reply(common: MgmReplyCommon) -> SimReply {
SimReply::new(Self::new(common))
}
}
}
// Simple model using i16 values. // Simple model using i16 values.
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct MgtDipole { pub struct MgtDipole {
@ -262,7 +323,7 @@ pub mod acs {
} }
impl SerializableSimMsgPayload<SimRequest> for MgtRequest { impl SerializableSimMsgPayload<SimRequest> for MgtRequest {
const TARGET: SimTarget = SimTarget::Mgt; const TARGET: SimComponent = SimComponent::Mgt;
} }
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
@ -279,78 +340,12 @@ pub mod acs {
} }
impl SerializableSimMsgPayload<SimReply> for MgtReply { impl SerializableSimMsgPayload<SimReply> for MgtReply {
const TARGET: SimTarget = SimTarget::Mgm; const TARGET: SimComponent = SimComponent::MgmLis3Mdl;
} }
} }
pub mod udp { pub mod udp {
use std::{ pub const SIM_CTRL_PORT: u16 = 7303;
net::{SocketAddr, UdpSocket},
time::Duration,
};
use thiserror::Error;
use crate::{SimReply, SimRequest};
#[derive(Error, Debug)]
pub enum ReceptionError {
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("Serde JSON error: {0}")]
SerdeJson(#[from] serde_json::Error),
}
pub struct SimUdpClient {
socket: UdpSocket,
pub reply_buf: [u8; 4096],
}
impl SimUdpClient {
pub fn new(
server_addr: &SocketAddr,
non_blocking: bool,
read_timeot_ms: Option<u64>,
) -> std::io::Result<Self> {
let socket = UdpSocket::bind("127.0.0.1:0")?;
socket.set_nonblocking(non_blocking)?;
socket
.connect(server_addr)
.expect("could not connect to server addr");
if let Some(read_timeout) = read_timeot_ms {
// Set a read timeout so the test does not hang on failures.
socket.set_read_timeout(Some(Duration::from_millis(read_timeout)))?;
}
Ok(Self {
socket,
reply_buf: [0; 4096],
})
}
pub fn set_nonblocking(&self, non_blocking: bool) -> std::io::Result<()> {
self.socket.set_nonblocking(non_blocking)
}
pub fn set_read_timeout(&self, read_timeout_ms: u64) -> std::io::Result<()> {
self.socket
.set_read_timeout(Some(Duration::from_millis(read_timeout_ms)))
}
pub fn send_request(&self, sim_request: &SimRequest) -> std::io::Result<usize> {
self.socket.send(
&serde_json::to_vec(sim_request).expect("conversion of request to vector failed"),
)
}
pub fn recv_raw(&mut self) -> std::io::Result<usize> {
self.socket.recv(&mut self.reply_buf)
}
pub fn recv_sim_reply(&mut self) -> Result<SimReply, ReceptionError> {
let read_len = self.recv_raw()?;
Ok(serde_json::from_slice(&self.reply_buf[0..read_len])?)
}
}
} }
#[cfg(test)] #[cfg(test)]
@ -363,7 +358,7 @@ pub mod tests {
} }
impl SerializableSimMsgPayload<SimRequest> for DummyRequest { impl SerializableSimMsgPayload<SimRequest> for DummyRequest {
const TARGET: SimTarget = SimTarget::SimCtrl; const TARGET: SimComponent = SimComponent::SimCtrl;
} }
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
@ -372,13 +367,13 @@ pub mod tests {
} }
impl SerializableSimMsgPayload<SimReply> for DummyReply { impl SerializableSimMsgPayload<SimReply> for DummyReply {
const TARGET: SimTarget = SimTarget::SimCtrl; const TARGET: SimComponent = SimComponent::SimCtrl;
} }
#[test] #[test]
fn test_basic_request() { fn test_basic_request() {
let sim_request = SimRequest::new_with_epoch_time(DummyRequest::Ping); let sim_request = SimRequest::new_with_epoch_time(DummyRequest::Ping);
assert_eq!(sim_request.target(), SimTarget::SimCtrl); assert_eq!(sim_request.component(), SimComponent::SimCtrl);
assert_eq!(sim_request.msg_type(), SimMessageType::Request); assert_eq!(sim_request.msg_type(), SimMessageType::Request);
let dummy_request = let dummy_request =
DummyRequest::from_sim_message(&sim_request).expect("deserialization failed"); DummyRequest::from_sim_message(&sim_request).expect("deserialization failed");
@ -388,7 +383,7 @@ pub mod tests {
#[test] #[test]
fn test_basic_reply() { fn test_basic_reply() {
let sim_reply = SimReply::new(DummyReply::Pong); let sim_reply = SimReply::new(DummyReply::Pong);
assert_eq!(sim_reply.target(), SimTarget::SimCtrl); assert_eq!(sim_reply.component(), SimComponent::SimCtrl);
assert_eq!(sim_reply.msg_type(), SimMessageType::Reply); assert_eq!(sim_reply.msg_type(), SimMessageType::Reply);
let dummy_request = let dummy_request =
DummyReply::from_sim_message(&sim_reply).expect("deserialization failed"); DummyReply::from_sim_message(&sim_reply).expect("deserialization failed");

View File

@ -3,7 +3,8 @@ use asynchronix::simulation::{Mailbox, SimInit};
use asynchronix::time::{MonotonicTime, SystemClock}; use asynchronix::time::{MonotonicTime, SystemClock};
use controller::SimController; use controller::SimController;
use eps::PcduModel; use eps::PcduModel;
use satrs_minisim::{SimReply, SimRequest, SIM_CTRL_UDP_PORT}; use satrs_minisim::udp::SIM_CTRL_PORT;
use satrs_minisim::{SimReply, SimRequest};
use std::sync::mpsc; use std::sync::mpsc;
use std::thread; use std::thread;
use std::time::{Duration, SystemTime}; use std::time::{Duration, SystemTime};
@ -30,7 +31,8 @@ fn create_sim_controller(
request_receiver: mpsc::Receiver<SimRequest>, request_receiver: mpsc::Receiver<SimRequest>,
) -> SimController { ) -> SimController {
// Instantiate models and their mailboxes. // Instantiate models and their mailboxes.
let mgm_model = MagnetometerModel::new(Duration::from_millis(50), reply_sender.clone()); let mgm_model =
MagnetometerModel::new_for_lis3mdl(Duration::from_millis(50), reply_sender.clone());
let mgm_mailbox = Mailbox::new(); let mgm_mailbox = Mailbox::new();
let mgm_addr = mgm_mailbox.address(); let mgm_addr = mgm_mailbox.address();
@ -112,9 +114,9 @@ fn main() {
}); });
let mut udp_server = let mut udp_server =
SimUdpServer::new(SIM_CTRL_UDP_PORT, request_sender, reply_receiver, 200, None) SimUdpServer::new(SIM_CTRL_PORT, request_sender, reply_receiver, 200, None)
.expect("could not create UDP request server"); .expect("could not create UDP request server");
log::info!("starting UDP server on port {}", SIM_CTRL_UDP_PORT); log::info!("starting UDP server on port {}", SIM_CTRL_PORT);
// This thread manages the simulator UDP server. // This thread manages the simulator UDP server.
let udp_tc_thread = thread::spawn(move || { let udp_tc_thread = thread::spawn(move || {
udp_server.run(); udp_server.run();