Merge remote-tracking branch 'origin/main' into shell-cmd-executor
This commit is contained in:
commit
a25b55baed
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -641,6 +641,7 @@ dependencies = [
|
|||||||
"log",
|
"log",
|
||||||
"mio",
|
"mio",
|
||||||
"num_enum",
|
"num_enum",
|
||||||
|
"once_cell",
|
||||||
"satrs",
|
"satrs",
|
||||||
"satrs-mib",
|
"satrs-mib",
|
||||||
"serde",
|
"serde",
|
||||||
|
@ -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"
|
||||||
|
@ -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(
|
||||||
|
@ -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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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};
|
||||||
|
@ -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(
|
||||||
|
@ -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!(
|
||||||
|
@ -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));
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user