Merge remote-tracking branch 'origin/main' into shell-cmd-executor

This commit is contained in:
Robin Müller 2024-04-25 20:23:23 +02:00
commit a25b55baed
8 changed files with 175 additions and 40 deletions

1
Cargo.lock generated
View File

@ -641,6 +641,7 @@ dependencies = [
"log", "log",
"mio", "mio",
"num_enum", "num_enum",
"once_cell",
"satrs", "satrs",
"satrs-mib", "satrs-mib",
"serde", "serde",

View File

@ -21,6 +21,7 @@ serde_json = "1"
mio = "0.8" mio = "0.8"
homedir = "0.2" homedir = "0.2"
socket2 = "0.5" socket2 = "0.5"
once_cell = "1.19"
[dependencies.satrs] [dependencies.satrs]
version = "0.2.0-rc.5" version = "0.2.0-rc.5"

View File

@ -96,6 +96,16 @@ def create_cmd_definition_tree() -> CmdTreeNode:
CmdTreeNode("custom_params", "Custom Camera Parameters as specified from file") CmdTreeNode("custom_params", "Custom Camera Parameters as specified from file")
) )
action_node.add_child(cam_node) action_node.add_child(cam_node)
controller_node = CmdTreeNode("controller", "Main OBSW Controller")
controller_node.add_child(
CmdTreeNode("downlink_logs", "Downlink Logs via toGround folder")
)
controller_node.add_child(
CmdTreeNode("downlink_last_img", "Downlink last image via toGroundLP folder")
)
action_node.add_child(controller_node)
root_node.add_child(action_node) root_node.add_child(action_node)
return root_node return root_node
@ -157,6 +167,18 @@ def pack_pus_telecommands(q: DefaultPusQueueHelper, cmd_path: str):
service=8, subservice=128, apid=EXPERIMENT_APID, app_data=data service=8, subservice=128, apid=EXPERIMENT_APID, app_data=data
) )
) )
if cmd_path_list[1] == "controller":
assert len(cmd_path_list) >= 3
data = bytearray()
if cmd_path_list[2] == "downlink_logs":
data.extend(make_action_cmd_header(UniqueId.Controller, 2))
if cmd_path_list[2] == "downlink_last_img":
data.extend(make_action_cmd_header(UniqueId.Controller, 3))
return q.add_pus_tc(
PusTelecommand(
service=8, subservice=128, apid=EXPERIMENT_APID, app_data=data
)
)
def handle_set_mode_cmd( def handle_set_mode_cmd(

View File

@ -9,7 +9,9 @@ use std::path::{Path, PathBuf};
pub const STOP_FILE_NAME: &str = "stop-experiment"; pub const STOP_FILE_NAME: &str = "stop-experiment";
pub const CONFIG_FILE_NAME: &str = "exp278.toml"; pub const CONFIG_FILE_NAME: &str = "exp278.toml";
pub const HOME_FOLDER_EXPERIMENT: &str = "/home/exp278"; pub const HOME_FOLDER_EXPERIMENT: &str = "/home/exp278"; // also where IMS-100 images are placed
pub const TO_GROUND_FOLDER_EXPERIMENT: &str = "/home/exp278/toGround";
pub const TO_GROUND_LP_FOLDER_EXPERIMENT: &str = "/home/exp278/toGroundLP";
pub const LOG_FOLDER: &str = "logs"; pub const LOG_FOLDER: &str = "logs";
pub const OBSW_SERVER_ADDR: Ipv4Addr = Ipv4Addr::UNSPECIFIED; pub const OBSW_SERVER_ADDR: Ipv4Addr = Ipv4Addr::UNSPECIFIED;
@ -313,3 +315,15 @@ pub mod tasks {
pub const STOP_CHECK_FREQUENCY_MS: u64 = 400; pub const STOP_CHECK_FREQUENCY_MS: u64 = 400;
pub const STOP_CHECK_FREQUENCY: Duration = Duration::from_millis(STOP_CHECK_FREQUENCY_MS); pub const STOP_CHECK_FREQUENCY: Duration = Duration::from_millis(STOP_CHECK_FREQUENCY_MS);
} }
pub fn create_low_priority_ground_dir() {
log::info!("creating low priority to ground directory");
if !Path::new(TO_GROUND_LP_FOLDER_EXPERIMENT).exists()
&& std::fs::create_dir_all(TO_GROUND_LP_FOLDER_EXPERIMENT).is_err()
{
log::error!(
"Failed to create low priority to ground directory '{}'",
TO_GROUND_LP_FOLDER_EXPERIMENT
);
}
}

View File

@ -1,6 +1,12 @@
use crate::logger::LOGFILE_PATH;
use num_enum::TryFromPrimitive; 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,
};
use satrs::action::ActionRequestVariant;
use satrs::{ use satrs::{
action::{ActionRequest, ActionRequestVariant}, action::ActionRequest,
params::Params, params::Params,
pus::action::{ActionReplyPus, ActionReplyVariant}, pus::action::{ActionReplyPus, ActionReplyVariant},
request::{GenericMessage, MessageMetadata}, request::{GenericMessage, MessageMetadata},
@ -14,10 +20,8 @@ use std::{
sync::{atomic::AtomicBool, mpsc, Arc}, sync::{atomic::AtomicBool, mpsc, Arc},
}; };
use ops_sat_rs::config::{ use ops_sat_rs::config::ctrl_err::{
action_err::INVALID_ACTION_ID, SHELL_CMD_EXECUTION_FAILURE, SHELL_CMD_INVALID_FORMAT, SHELL_CMD_IO_ERROR,
ctrl_err::{SHELL_CMD_EXECUTION_FAILURE, SHELL_CMD_INVALID_FORMAT, SHELL_CMD_IO_ERROR},
HOME_PATH, STOP_FILE_NAME,
}; };
use crate::requests::CompositeRequest; use crate::requests::CompositeRequest;
@ -32,7 +36,9 @@ pub struct ShellCmd<'a> {
#[repr(u32)] #[repr(u32)]
pub enum ActionId { pub enum ActionId {
StopExperiment = 1, StopExperiment = 1,
ExecuteShellCommandBlocking = 2, DownlinkLogfile = 2,
DownlinkImages = 3,
ExecuteShellCommandBlocking = 4,
} }
pub struct ExperimentController { pub struct ExperimentController {
@ -84,34 +90,6 @@ impl ExperimentController {
self.check_stop_file(); self.check_stop_file();
} }
pub fn send_completion_success(&self, requestor: &MessageMetadata, action_req: &ActionRequest) {
let result = self.action_reply_tx.send(GenericMessage::new_action_reply(
*requestor,
action_req.action_id,
ActionReplyVariant::Completed,
));
if result.is_err() {
log::error!("sending action reply failed");
}
}
pub fn send_completion_failure(
&self,
requestor: &MessageMetadata,
action_req: &ActionRequest,
error_code: ResultU16,
params: Option<Params>,
) {
let result = self.action_reply_tx.send(GenericMessage::new_action_reply(
*requestor,
action_req.action_id,
ActionReplyVariant::CompletionFailed { error_code, params },
));
if result.is_err() {
log::error!("sending action reply failed");
}
}
pub fn handle_action_request(&mut self, requestor: MessageMetadata, action_req: ActionRequest) { pub fn handle_action_request(&mut self, requestor: MessageMetadata, action_req: ActionRequest) {
let send_completion_failure = |error_code: ResultU16, params: Option<Params>| { let send_completion_failure = |error_code: ResultU16, params: Option<Params>| {
let result = self.action_reply_tx.send(GenericMessage::new_action_reply( let result = self.action_reply_tx.send(GenericMessage::new_action_reply(
@ -137,6 +115,72 @@ impl ExperimentController {
ActionId::ExecuteShellCommandBlocking => { ActionId::ExecuteShellCommandBlocking => {
self.handle_shell_command_execution(&requestor, &action_req); self.handle_shell_command_execution(&requestor, &action_req);
} }
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) = <PathBuf as Clone>::clone(&image_path)
.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")
}
}
}
}
}
}
pub fn handle_downlink_logfile(&self, requestor: &MessageMetadata, action_req: &ActionRequest) {
log::info!("copying logfile into downlink folder");
if let Some(logfile_path) = LOGFILE_PATH.get() {
if let Ok(logfile_path) = <PathBuf as Clone>::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)
}
} else {
log::warn!("downlink path emtpy")
}
}
pub fn send_completion_success(&self, requestor: &MessageMetadata, action_req: &ActionRequest) {
let result = self.action_reply_tx.send(GenericMessage::new_action_reply(
*requestor,
action_req.action_id,
ActionReplyVariant::Completed,
));
if result.is_err() {
log::error!("sending action reply failed");
}
}
pub fn send_completion_failure(
&self,
requestor: &MessageMetadata,
action_req: &ActionRequest,
error_code: ResultU16,
params: Option<Params>,
) {
let result = self.action_reply_tx.send(GenericMessage::new_action_reply(
*requestor,
action_req.action_id,
ActionReplyVariant::CompletionFailed { error_code, params },
));
if result.is_err() {
log::error!("sending action reply failed");
} }
} }
@ -177,6 +221,14 @@ impl ExperimentController {
} }
Err(e) => { Err(e) => {
log::warn!("failed to deserialize shell command: {}", e); log::warn!("failed to deserialize shell command: {}", e);
let result = self.action_reply_tx.send(GenericMessage::new_action_reply(
*requestor,
action_req.action_id,
ActionReplyVariant::Completed,
));
if result.is_err() {
log::error!("Sending action reply failed");
}
} }
} }
} else { } else {
@ -210,6 +262,37 @@ impl ExperimentController {
} }
} }
// TODO no idea if this works in any way shape or form
pub fn get_latest_image(index: usize) -> Result<PathBuf, std::io::Error> {
// Get the most recently modified file
let mut png_files = std::fs::read_dir(HOME_FOLDER_EXPERIMENT)?
.flatten()
.filter(|f| match f.metadata() {
Ok(metadata) => metadata.is_file(),
Err(_) => false,
})
.filter(|f| match f.file_name().into_string() {
Ok(name) => name.ends_with(".png"),
Err(_) => false,
})
.collect::<Vec<std::fs::DirEntry>>();
png_files.sort_by_key(|x| match x.metadata() {
Ok(metadata) => {
if let Ok(time) = metadata.modified() {
time
} else {
std::time::SystemTime::UNIX_EPOCH
}
}
Err(_) => std::time::SystemTime::UNIX_EPOCH,
});
png_files.reverse();
if let Some(png) = png_files.into_iter().nth(index) {
return Ok(png.path());
}
Err(std::io::Error::other("No latest image found"))
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::sync::{mpsc, Arc}; use std::sync::{mpsc, Arc};

View File

@ -291,7 +291,7 @@ impl IMS100BatchHandler {
}, },
}; };
let output = self.take_picture(param)?; let output = self.take_picture(param)?;
debug!("Sending action reply!"); info!("Sending action reply!");
send_data_reply(self.id, output.stdout, &self.stamp_helper, &self.tm_tx)?; send_data_reply(self.id, output.stdout, &self.stamp_helper, &self.tm_tx)?;
self.action_reply_tx self.action_reply_tx
.send(GenericMessage::new( .send(GenericMessage::new(

View File

@ -1,13 +1,25 @@
use once_cell::sync::OnceCell;
use ops_sat_rs::config::LOG_FOLDER;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use ops_sat_rs::config::LOG_FOLDER; pub static LOGFILE_PATH: OnceCell<PathBuf> = OnceCell::new();
pub fn setup_logger() -> Result<(), fern::InitError> { pub fn setup_logger() -> Result<(), fern::InitError> {
if !Path::new(LOG_FOLDER).exists() && std::fs::create_dir_all(LOG_FOLDER).is_err() { if !Path::new(LOG_FOLDER).exists() && std::fs::create_dir_all(LOG_FOLDER).is_err() {
eprintln!("Failed to create log folder '{}'", LOG_FOLDER); eprintln!("Failed to create log folder '{}'", LOG_FOLDER);
} }
let mut path_buf = PathBuf::from(LOG_FOLDER); let mut path_buf = PathBuf::from(LOG_FOLDER);
path_buf.push("output.log"); path_buf.push(
format!(
"output_{}.log",
humantime::format_rfc3339_seconds(std::time::SystemTime::now())
)
.replace(':', "_"),
);
println!("Creating logfile {:?}", path_buf);
LOGFILE_PATH
.set(path_buf.clone())
.expect("Error setting global logfile path");
fern::Dispatch::new() fern::Dispatch::new()
.format(move |out, message, record| { .format(move |out, message, record| {
out.finish(format_args!( out.finish(format_args!(

View File

@ -9,6 +9,7 @@ use log::info;
use ops_sat_rs::config::{ use ops_sat_rs::config::{
cfg_file::create_app_config, cfg_file::create_app_config,
components::{CONTROLLER_ID, TCP_SERVER, TCP_SPP_CLIENT, UDP_SERVER}, components::{CONTROLLER_ID, TCP_SERVER, TCP_SPP_CLIENT, UDP_SERVER},
create_low_priority_ground_dir,
pool::create_sched_tc_pool, pool::create_sched_tc_pool,
tasks::{FREQ_MS_CAMERA_HANDLING, FREQ_MS_CTRL, FREQ_MS_PUS_STACK, STOP_CHECK_FREQUENCY}, tasks::{FREQ_MS_CAMERA_HANDLING, FREQ_MS_CTRL, FREQ_MS_PUS_STACK, STOP_CHECK_FREQUENCY},
VALID_PACKET_ID_LIST, VERSION, VALID_PACKET_ID_LIST, VERSION,
@ -52,12 +53,13 @@ mod requests;
mod tmtc; mod tmtc;
fn main() { fn main() {
setup_logger().expect("setting up logging with fern failed");
let version_str = VERSION.unwrap_or("?"); let version_str = VERSION.unwrap_or("?");
println!("OPS-SAT Rust Experiment OBSW v{}", version_str); println!("OPS-SAT Rust Experiment OBSW v{}", version_str);
setup_logger().expect("setting up logging with fern failed");
create_low_priority_ground_dir();
let app_cfg = create_app_config(); let app_cfg = create_app_config();
println!("App Configuration: {:?}", app_cfg); info!("App Configuration: {:?}", app_cfg);
let stop_signal = Arc::new(AtomicBool::new(false)); let stop_signal = Arc::new(AtomicBool::new(false));