diff --git a/src/config.rs b/src/config.rs index a1284b4..0804ffc 100644 --- a/src/config.rs +++ b/src/config.rs @@ -227,6 +227,13 @@ pub mod ctrl_err { pub const SHELL_CMD_EXECUTION_FAILURE: ResultU16 = ResultU16::new(GroupId::Controller as u8, 2); #[resultcode] pub const SHELL_CMD_INVALID_FORMAT: ResultU16 = ResultU16::new(GroupId::Controller as u8, 3); + // TODO: Probably could be in a dedicated modules for these returnvalues. + #[resultcode] + pub const FILESYSTEM_COPY_ERROR: ResultU16 = ResultU16::new(GroupId::Controller as u8, 4); + #[resultcode] + pub const IMAGE_NOT_FOUND_FOR_COPY: ResultU16 = ResultU16::new(GroupId::Controller as u8, 5); + #[resultcode] + pub const INVALID_LOGFILE_PATH: ResultU16 = ResultU16::new(GroupId::Controller as u8, 6); pub const CTRL_ERR_RESULTS: &[ResultU16Info] = &[ INVALID_CMD_FORMAT_EXT, diff --git a/src/controller.rs b/src/controller.rs index 75c7c0f..2e91915 100644 --- a/src/controller.rs +++ b/src/controller.rs @@ -1,9 +1,9 @@ use crate::logger::LOGFILE_PATH; use num_enum::TryFromPrimitive; use ops_sat_rs::config::{ - action_err::INVALID_ACTION_ID, HOME_FOLDER_EXPERIMENT, HOME_PATH, STOP_FILE_NAME, - TO_GROUND_FOLDER_EXPERIMENT, + action_err::INVALID_ACTION_ID, HOME_FOLDER_EXPERIMENT, TO_GROUND_FOLDER_EXPERIMENT, }; +use ops_sat_rs::config::{HOME_PATH, STOP_FILE_NAME, TO_GROUND_LP_FOLDER_EXPERIMENT}; use satrs::action::ActionRequestVariant; use satrs::{ action::ActionRequest, @@ -13,14 +13,15 @@ use satrs::{ res_code::ResultU16, }; use serde::{Deserialize, Serialize}; +use std::env::temp_dir; use std::{ - env::temp_dir, path::{Path, PathBuf}, process::Command, sync::{atomic::AtomicBool, mpsc, Arc}, }; use ops_sat_rs::config::ctrl_err::{ + FILESYSTEM_COPY_ERROR, IMAGE_NOT_FOUND_FOR_COPY, INVALID_LOGFILE_PATH, SHELL_CMD_EXECUTION_FAILURE, SHELL_CMD_INVALID_FORMAT, SHELL_CMD_IO_ERROR, }; @@ -41,12 +42,34 @@ pub enum ActionId { ExecuteShellCommandBlocking = 4, } +#[derive(Debug)] +pub struct ControllerPathCollection { + pub stop_file_home_path: PathBuf, + pub stop_file_tmp_path: PathBuf, + pub to_ground_dir: PathBuf, + pub to_ground_low_prio_dir: PathBuf, +} + +impl Default for ControllerPathCollection { + fn default() -> Self { + let mut home_path_stop_file = PathBuf::new(); + home_path_stop_file.push(HOME_PATH.as_path()); + home_path_stop_file.push(STOP_FILE_NAME); + let mut tmp_path_stop_file = temp_dir(); + tmp_path_stop_file.push(STOP_FILE_NAME); + Self { + stop_file_home_path: home_path_stop_file, + stop_file_tmp_path: tmp_path_stop_file, + to_ground_dir: PathBuf::from(TO_GROUND_FOLDER_EXPERIMENT), + to_ground_low_prio_dir: PathBuf::from(TO_GROUND_LP_FOLDER_EXPERIMENT), + } + } +} pub struct ExperimentController { pub composite_request_rx: mpsc::Receiver>, pub action_reply_tx: mpsc::Sender>, pub stop_signal: Arc, - home_path_stop_file: PathBuf, - tmp_path_stop_file: PathBuf, + pub paths: ControllerPathCollection, } impl ExperimentController { @@ -54,18 +77,13 @@ impl ExperimentController { composite_request_rx: mpsc::Receiver>, action_reply_tx: mpsc::Sender>, stop_signal: Arc, + paths: ControllerPathCollection, ) -> Self { - let mut home_path_stop_file = PathBuf::new(); - home_path_stop_file.push(HOME_PATH.as_path()); - home_path_stop_file.push(STOP_FILE_NAME); - let mut tmp_path_stop_file = temp_dir(); - tmp_path_stop_file.push(STOP_FILE_NAME); Self { composite_request_rx, action_reply_tx, stop_signal, - home_path_stop_file, - tmp_path_stop_file, + paths, } } } @@ -117,45 +135,77 @@ impl ExperimentController { } ActionId::DownlinkLogfile => self.handle_downlink_logfile(&requestor, &action_req), // downlink images, default will be the last image, otherwise specified counting down (2 = second to last image, etc.) - ActionId::DownlinkImages => { - log::info!("Copying images into low priority downlink folder"); - if let Ok(image_path) = match action_req.variant { - ActionRequestVariant::VecData(data) => { - let index = data[0]; - get_latest_image(index as usize) - } - _ => get_latest_image(0), - } { - if let Ok(image_path) = image_path.clone() - .into_os_string() - .into_string() - { - if std::fs::copy(image_path, TO_GROUND_FOLDER_EXPERIMENT).is_err() { - log::error!("Copying logfile into downlink path failed") - } - } - } + ActionId::DownlinkImages => self.handle_downlink_cam_image(&requestor, &action_req), + } + } + + pub fn handle_downlink_cam_image( + &self, + requestor: &MessageMetadata, + action_req: &ActionRequest, + ) { + log::info!("copying images into low priority downlink folder"); + let image_path_result = match &action_req.variant { + ActionRequestVariant::VecData(data) => { + let index = data[0]; + get_latest_image(index as usize) + } + _ => get_latest_image(0), + }; + match image_path_result { + Ok(image_path) => self.handle_file_copy( + requestor, + action_req, + &image_path, + &self.paths.to_ground_dir, + ), + Err(e) => { + log::warn!("could not retrieve image path: {}", e); + self.send_completion_failure( + requestor, + action_req, + IMAGE_NOT_FOUND_FOR_COPY, + Some(e.to_string().into()), + ); } } } pub fn handle_downlink_logfile(&self, requestor: &MessageMetadata, action_req: &ActionRequest) { - log::info!("copying logfile into downlink folder"); + log::info!("copying logfile into {:?}", self.paths.to_ground_dir); if let Some(logfile_path) = LOGFILE_PATH.get() { - if let Ok(logfile_path) = ::clone(logfile_path) - .into_os_string() - .into_string() - { - if std::fs::copy(logfile_path.as_str(), TO_GROUND_FOLDER_EXPERIMENT).is_err() { - log::warn!("copying logfile into downlink path failed") - } - self.send_completion_success(requestor, action_req) - } + self.handle_file_copy( + requestor, + action_req, + logfile_path, + &self.paths.to_ground_dir, + ) } else { - log::warn!("downlink path emtpy") + log::error!("downlink path emtpy"); + self.send_completion_failure(requestor, action_req, INVALID_LOGFILE_PATH, None); } } + pub fn handle_file_copy( + &self, + requestor: &MessageMetadata, + action_req: &ActionRequest, + source_path: &Path, + target_path: &Path, + ) { + if let Err(e) = std::fs::copy(source_path, target_path) { + log::warn!("copying logfile into downlink path failed: {}", e); + self.send_completion_failure( + requestor, + action_req, + FILESYSTEM_COPY_ERROR, + Some(e.to_string().into()), + ); + return; + } + self.send_completion_success(requestor, action_req) + } + pub fn send_completion_success(&self, requestor: &MessageMetadata, action_req: &ActionRequest) { let result = self.action_reply_tx.send(GenericMessage::new_action_reply( *requestor, @@ -257,8 +307,8 @@ impl ExperimentController { .store(true, std::sync::atomic::Ordering::Relaxed); } }; - check_at_path(self.tmp_path_stop_file.as_path()); - check_at_path(self.home_path_stop_file.as_path()); + check_at_path(self.paths.stop_file_tmp_path.as_path()); + check_at_path(self.paths.stop_file_home_path.as_path()); } } @@ -293,6 +343,7 @@ pub fn get_latest_image(index: usize) -> Result { } Err(std::io::Error::other("No latest image found")) } + #[cfg(test)] mod tests { use std::sync::{mpsc, Arc}; @@ -304,15 +355,54 @@ mod tests { fn init() { env_logger::builder().is_test(true).init(); } + pub struct ControllerTestbench { + pub composite_req_tx: mpsc::Sender>, + pub action_reply_rx: mpsc::Receiver>, + pub stop_signal: Arc, + pub ctrl: ExperimentController, + } + + impl ControllerTestbench { + pub fn new() -> Self { + init(); + let (composite_req_tx, composite_req_rx) = mpsc::channel(); + let (action_reply_tx, action_reply_rx) = mpsc::channel(); + let stop_signal = Arc::new(AtomicBool::new(false)); + let test_tmp_dir = tempfile::tempdir().expect("creating tmpdir failed"); + let base_dir = PathBuf::from(test_tmp_dir.path()); + let mut stop_file_tmp_path = base_dir.clone(); + stop_file_tmp_path.push(STOP_FILE_NAME); + let mut stop_file_home_path = base_dir.clone(); + stop_file_home_path.push("home"); + stop_file_home_path.push(STOP_FILE_NAME); + let mut to_ground_dir = base_dir.clone(); + to_ground_dir.push("toGround"); + let mut to_ground_low_prio_dir = base_dir.clone(); + to_ground_low_prio_dir.push("toGroundLP"); + + let test_paths = ControllerPathCollection { + stop_file_home_path, + stop_file_tmp_path, + to_ground_dir, + to_ground_low_prio_dir, + }; + ControllerTestbench { + composite_req_tx, + action_reply_rx, + stop_signal: stop_signal.clone(), + ctrl: ExperimentController::new( + composite_req_rx, + action_reply_tx, + stop_signal, + test_paths, + ), + } + } + } #[test] fn test_shell_cmd_exection() { - init(); - let (composite_req_tx, composite_req_rx) = mpsc::channel(); - let (action_reply_tx, action_reply_rx) = mpsc::channel(); - let stop_signal = Arc::default(); - let mut exp_ctrl = - ExperimentController::new(composite_req_rx, action_reply_tx, stop_signal); + let mut testbench = ControllerTestbench::new(); let named_temp_file = NamedTempFile::new().expect("creating temp file failed"); let args = vec![named_temp_file .path() @@ -325,15 +415,17 @@ mod tests { action_id: ActionId::ExecuteShellCommandBlocking as u32, variant: satrs::action::ActionRequestVariant::VecData(cmd_serialized.into_bytes()), }; - composite_req_tx + testbench + .composite_req_tx .send(GenericMessage::new( MessageMetadata::new(1, 2), CompositeRequest::Action(action_req), )) .expect("sending action request failed"); - exp_ctrl.perform_operation(); + testbench.ctrl.perform_operation(); assert!(!named_temp_file.path().exists()); - let action_reply = action_reply_rx + let action_reply = testbench + .action_reply_rx .try_recv() .expect("receiving action reply failed"); assert_eq!( @@ -351,3 +443,59 @@ mod tests { } } } + +// Need to think about the value of this again. This is not easy to do in Rust.. +/* +pub trait ActionHelperHook { + fn is_valid_action_id(&self, action_id: satrs::action::ActionId) -> bool; + fn send_reply(&self, action_reply: GenericActionReplyPus) -> Result<(), GenericSendError>; +} + +pub struct ActionHelper { + pub requestor: MessageMetadata, + pub action_id: satrs::action::ActionId, + pub user_hook: Hook, +} + +impl ActionHelper { + fn new( + &mut self, + requestor: MessageMetadata, + action_id: satrs::action::ActionId, + ) -> Result, GenericSendError> { + if !self.user_hook.is_valid_action_id(action_id) { + self.report_completion_failed(INVALID_ACTION_ID, None)?; + return Ok(None); + } + Ok(Some(Self { + requestor, + action_id + + + + })) + } + + fn report_completion_success(&self) -> Result<(), GenericSendError> { + self.user_hook.send_reply(GenericMessage::new_action_reply( + self.requestor, + self.action_id, + ActionReplyVariant::Completed, + ))?; + Ok(()) + } + + fn report_completion_failed( + &self, + error_code: ResultU16, + params: Option, + ) -> Result<(), GenericSendError> { + self.user_hook.send_reply(GenericMessage::new_action_reply( + self.requestor, + self.action_id, + ActionReplyVariant::CompletionFailed { error_code, params }, + ))?; + Ok(()) + } +} +*/ diff --git a/src/main.rs b/src/main.rs index 9612ed9..c82ec91 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,7 @@ use std::{ + env::temp_dir, net::{IpAddr, SocketAddr}, + path::PathBuf, sync::{atomic::AtomicBool, mpsc, Arc}, thread, time::Duration, @@ -12,7 +14,7 @@ use ops_sat_rs::config::{ create_low_priority_ground_dir, pool::create_sched_tc_pool, tasks::{FREQ_MS_CAMERA_HANDLING, FREQ_MS_CTRL, FREQ_MS_PUS_STACK, STOP_CHECK_FREQUENCY}, - VALID_PACKET_ID_LIST, VERSION, + HOME_PATH, STOP_FILE_NAME, VALID_PACKET_ID_LIST, VERSION, }; use ops_sat_rs::config::{components::CAMERA_HANDLER, tasks::FREQ_MS_EVENT_HANDLING}; use ops_sat_rs::config::{tasks::FREQ_MS_UDP_TMTC, OBSW_SERVER_ADDR, SERVER_PORT}; @@ -22,7 +24,7 @@ use satrs::{ pus::event_man::EventRequestWithToken, }; -use crate::tmtc::tm_sink::TmFunnelDynamic; +use crate::{controller::ControllerPathCollection, tmtc::tm_sink::TmFunnelDynamic}; use crate::{controller::ExperimentController, pus::test::create_test_service}; use crate::{ events::EventHandler, @@ -185,10 +187,16 @@ fn main() { stop_signal.clone(), ); + let mut home_path_stop_file = PathBuf::new(); + home_path_stop_file.push(HOME_PATH.as_path()); + home_path_stop_file.push(STOP_FILE_NAME); + let mut tmp_path_stop_file = temp_dir(); + tmp_path_stop_file.push(STOP_FILE_NAME); let mut controller = ExperimentController::new( controller_composite_rx, pus_action_reply_tx.clone(), stop_signal.clone(), + ControllerPathCollection::default(), ); let mut tcp_spp_client = TcpSppClientStd::new(