take image metadata generation #30
17
Cargo.lock
generated
17
Cargo.lock
generated
@ -504,12 +504,6 @@ dependencies = [
|
|||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "lazy_static"
|
|
||||||
version = "1.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.154"
|
version = "0.2.154"
|
||||||
@ -636,7 +630,6 @@ dependencies = [
|
|||||||
"fern",
|
"fern",
|
||||||
"homedir",
|
"homedir",
|
||||||
"humantime",
|
"humantime",
|
||||||
"lazy_static",
|
|
||||||
"log",
|
"log",
|
||||||
"mio",
|
"mio",
|
||||||
"num_enum",
|
"num_enum",
|
||||||
@ -776,7 +769,7 @@ checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "satrs"
|
name = "satrs"
|
||||||
version = "0.2.0-rc.5"
|
version = "0.2.0-rc.5"
|
||||||
source = "git+https://egit.irs.uni-stuttgart.de/rust/sat-rs.git?branch=main#29f71c2a571e7492cf5d997d4c11c3f844de83bc"
|
source = "git+https://egit.irs.uni-stuttgart.de/rust/sat-rs.git?branch=main#424dfc439c52223c8480e67413c8305dad5a75dc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bus",
|
"bus",
|
||||||
"cobs",
|
"cobs",
|
||||||
@ -835,9 +828,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.199"
|
version = "1.0.200"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0c9f6e76df036c77cd94996771fb40db98187f096dd0b9af39c6c6e452ba966a"
|
checksum = "ddc6f9cc94d67c0e21aaf7eda3a010fd3af78ebf6e096aa6e2e13c79749cce4f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
@ -855,9 +848,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.199"
|
version = "1.0.200"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "11bd257a6541e141e42ca6d24ae26f7714887b47e89aa739099104c7e4d3b7fc"
|
checksum = "856f046b9400cee3c8c94ed572ecdb752444c24528c035cd35882aad6f492bcb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -10,7 +10,6 @@ fern = "0.6"
|
|||||||
toml = "0.8"
|
toml = "0.8"
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
lazy_static = "1"
|
|
||||||
humantime = "2"
|
humantime = "2"
|
||||||
strum = { version = "0.26", features = ["derive"] }
|
strum = { version = "0.26", features = ["derive"] }
|
||||||
thiserror = "1"
|
thiserror = "1"
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
use lazy_static::lazy_static;
|
|
||||||
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
use satrs::events::{EventU32TypedSev, SeverityInfo};
|
use satrs::events::{EventU32TypedSev, SeverityInfo};
|
||||||
@ -29,8 +28,11 @@ pub const VALID_PACKET_ID_LIST: &[PacketId] = &[PacketId::new_for_tc(true, EXPER
|
|||||||
pub const SPP_CLIENT_WIRETAPPING_RX: bool = false;
|
pub const SPP_CLIENT_WIRETAPPING_RX: bool = false;
|
||||||
pub const SPP_CLIENT_WIRETAPPING_TX: bool = false;
|
pub const SPP_CLIENT_WIRETAPPING_TX: bool = false;
|
||||||
|
|
||||||
|
pub const VERSION: Option<&str> = option_env!("CARGO_PKG_VERSION");
|
||||||
|
|
||||||
pub static TO_GROUND_FOLDER_DIR: OnceCell<PathBuf> = OnceCell::new();
|
pub static TO_GROUND_FOLDER_DIR: OnceCell<PathBuf> = OnceCell::new();
|
||||||
pub static TO_GROUND_LP_FOLDER_DIR: OnceCell<PathBuf> = OnceCell::new();
|
pub static TO_GROUND_LP_FOLDER_DIR: OnceCell<PathBuf> = OnceCell::new();
|
||||||
|
pub static HOME_PATH: OnceCell<PathBuf> = OnceCell::new();
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, TryFromPrimitive, IntoPrimitive)]
|
#[derive(Copy, Clone, PartialEq, Eq, Debug, TryFromPrimitive, IntoPrimitive)]
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
@ -53,31 +55,32 @@ pub enum GroupId {
|
|||||||
pub const TEST_EVENT: EventU32TypedSev<SeverityInfo> =
|
pub const TEST_EVENT: EventU32TypedSev<SeverityInfo> =
|
||||||
EventU32TypedSev::<SeverityInfo>::new(GroupId::Tmtc as u16, 0);
|
EventU32TypedSev::<SeverityInfo>::new(GroupId::Tmtc as u16, 0);
|
||||||
|
|
||||||
pub const VERSION: Option<&str> = option_env!("CARGO_PKG_VERSION");
|
pub fn set_up_home_path() {
|
||||||
|
let mut home_path = PathBuf::new();
|
||||||
|
let home_path_default = homedir::get_my_home()
|
||||||
|
.expect("Getting home dir from OS failed.")
|
||||||
|
.expect("No home dir found.");
|
||||||
|
|
||||||
lazy_static! {
|
home_path.push(if Path::new(HOME_FOLDER_EXPERIMENT).exists() {
|
||||||
pub static ref HOME_PATH: PathBuf = {
|
HOME_FOLDER_EXPERIMENT
|
||||||
let mut home_path = PathBuf::new();
|
} else {
|
||||||
let home_path_default = homedir::get_my_home()
|
home_path_default
|
||||||
.expect("Getting home dir from OS failed.")
|
.to_str()
|
||||||
.expect("No home dir found.");
|
.expect("Error converting to string.")
|
||||||
|
});
|
||||||
home_path.push(if Path::new(HOME_FOLDER_EXPERIMENT).exists() {
|
HOME_PATH
|
||||||
HOME_FOLDER_EXPERIMENT
|
.set(home_path)
|
||||||
} else {
|
.expect("attempting to set once cell twice")
|
||||||
home_path_default
|
|
||||||
.to_str()
|
|
||||||
.expect("Error converting to string.")
|
|
||||||
});
|
|
||||||
home_path
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_up_low_prio_ground_dir() {
|
pub fn set_up_low_prio_ground_dir(base_path: PathBuf) {
|
||||||
|
/*
|
||||||
#[cfg(feature = "host")]
|
#[cfg(feature = "host")]
|
||||||
let mut to_ground_lp_dir = std::env::current_dir().expect("getting current dir failed");
|
let mut to_ground_lp_dir = std::env::current_dir().expect("getting current dir failed");
|
||||||
#[cfg(not(feature = "host"))]
|
#[cfg(not(feature = "host"))]
|
||||||
let mut to_ground_lp_dir = HOME_PATH.clone();
|
let mut to_ground_lp_dir = home_path;
|
||||||
|
*/
|
||||||
|
let mut to_ground_lp_dir = base_path.to_path_buf();
|
||||||
to_ground_lp_dir.push(TO_GROUND_LP_FOLDER_NAME);
|
to_ground_lp_dir.push(TO_GROUND_LP_FOLDER_NAME);
|
||||||
if !Path::new(&to_ground_lp_dir).exists() {
|
if !Path::new(&to_ground_lp_dir).exists() {
|
||||||
log::info!(
|
log::info!(
|
||||||
@ -96,11 +99,14 @@ pub fn set_up_low_prio_ground_dir() {
|
|||||||
.expect("attemting to set once cell twice");
|
.expect("attemting to set once cell twice");
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_up_ground_dir() {
|
pub fn set_up_ground_dir(base_path: PathBuf) {
|
||||||
|
/*
|
||||||
#[cfg(feature = "host")]
|
#[cfg(feature = "host")]
|
||||||
let mut to_ground_dir = std::env::current_dir().expect("getting current dir failed");
|
let mut to_ground_dir = std::env::current_dir().expect("getting current dir failed");
|
||||||
#[cfg(not(feature = "host"))]
|
#[cfg(not(feature = "host"))]
|
||||||
let mut to_ground_dir = HOME_PATH.clone();
|
let mut to_ground_dir = HOME_PATH.clone();
|
||||||
|
*/
|
||||||
|
let mut to_ground_dir = base_path.to_path_buf();
|
||||||
to_ground_dir.push(TO_GROUND_FOLDER_NAME);
|
to_ground_dir.push(TO_GROUND_FOLDER_NAME);
|
||||||
if !Path::new(&to_ground_dir).exists() {
|
if !Path::new(&to_ground_dir).exists() {
|
||||||
log::info!("creating to ground directory at {:?}", to_ground_dir);
|
log::info!("creating to ground directory at {:?}", to_ground_dir);
|
||||||
@ -123,7 +129,7 @@ pub mod cfg_file {
|
|||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{CONFIG_FILE_NAME, HOME_PATH, TCP_SPP_SERVER_PORT};
|
use super::{CONFIG_FILE_NAME, TCP_SPP_SERVER_PORT};
|
||||||
|
|
||||||
pub const SPP_CLIENT_PORT_CFG_KEY: &str = "tcp_spp_server_port";
|
pub const SPP_CLIENT_PORT_CFG_KEY: &str = "tcp_spp_server_port";
|
||||||
|
|
||||||
@ -140,8 +146,8 @@ pub mod cfg_file {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_app_config() -> AppCfg {
|
pub fn create_app_config(base_path: PathBuf) -> AppCfg {
|
||||||
let mut cfg_path = HOME_PATH.clone();
|
let mut cfg_path = base_path;
|
||||||
cfg_path.push(CONFIG_FILE_NAME);
|
cfg_path.push(CONFIG_FILE_NAME);
|
||||||
let cfg_path_home = cfg_path.as_path();
|
let cfg_path_home = cfg_path.as_path();
|
||||||
let relevant_path = if Path::new(CONFIG_FILE_NAME).exists() {
|
let relevant_path = if Path::new(CONFIG_FILE_NAME).exists() {
|
||||||
|
@ -47,6 +47,7 @@ pub enum ActionId {
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ControllerPathCollection {
|
pub struct ControllerPathCollection {
|
||||||
|
pub home_path: PathBuf,
|
||||||
pub stop_file_home_path: PathBuf,
|
pub stop_file_home_path: PathBuf,
|
||||||
pub stop_file_tmp_path: PathBuf,
|
pub stop_file_tmp_path: PathBuf,
|
||||||
pub to_ground_dir: PathBuf,
|
pub to_ground_dir: PathBuf,
|
||||||
@ -55,12 +56,14 @@ pub struct ControllerPathCollection {
|
|||||||
|
|
||||||
impl Default for ControllerPathCollection {
|
impl Default for ControllerPathCollection {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
|
let home_path = HOME_PATH.get().unwrap();
|
||||||
let mut home_path_stop_file = PathBuf::new();
|
let mut home_path_stop_file = PathBuf::new();
|
||||||
home_path_stop_file.push(HOME_PATH.as_path());
|
home_path_stop_file.push(home_path);
|
||||||
home_path_stop_file.push(STOP_FILE_NAME);
|
home_path_stop_file.push(STOP_FILE_NAME);
|
||||||
let mut tmp_path_stop_file = temp_dir();
|
let mut tmp_path_stop_file = temp_dir();
|
||||||
tmp_path_stop_file.push(STOP_FILE_NAME);
|
tmp_path_stop_file.push(STOP_FILE_NAME);
|
||||||
Self {
|
Self {
|
||||||
|
home_path: home_path.clone(),
|
||||||
stop_file_home_path: home_path_stop_file,
|
stop_file_home_path: home_path_stop_file,
|
||||||
stop_file_tmp_path: tmp_path_stop_file,
|
stop_file_tmp_path: tmp_path_stop_file,
|
||||||
to_ground_dir: TO_GROUND_FOLDER_DIR
|
to_ground_dir: TO_GROUND_FOLDER_DIR
|
||||||
@ -159,7 +162,7 @@ impl ExperimentController {
|
|||||||
) -> io::Result<()> {
|
) -> io::Result<()> {
|
||||||
log::info!("moving images into low priority downlink folder");
|
log::info!("moving images into low priority downlink folder");
|
||||||
let num_moved_files = move_images_inside_home_dir_to_low_prio_ground_dir(
|
let num_moved_files = move_images_inside_home_dir_to_low_prio_ground_dir(
|
||||||
&HOME_PATH,
|
HOME_PATH.get().unwrap(),
|
||||||
&self.paths.to_ground_low_prio_dir,
|
&self.paths.to_ground_low_prio_dir,
|
||||||
)?;
|
)?;
|
||||||
log::info!("moved {} image files", num_moved_files);
|
log::info!("moved {} image files", num_moved_files);
|
||||||
@ -405,6 +408,7 @@ mod tests {
|
|||||||
to_ground_low_prio_dir.push("toGroundLP");
|
to_ground_low_prio_dir.push("toGroundLP");
|
||||||
|
|
||||||
let test_paths = ControllerPathCollection {
|
let test_paths = ControllerPathCollection {
|
||||||
|
home_path: test_tmp_dir.path().to_path_buf(),
|
||||||
stop_file_home_path,
|
stop_file_home_path,
|
||||||
stop_file_tmp_path,
|
stop_file_tmp_path,
|
||||||
to_ground_dir,
|
to_ground_dir,
|
||||||
|
@ -30,7 +30,7 @@ use derive_new::new;
|
|||||||
use log::info;
|
use log::info;
|
||||||
use num_enum::TryFromPrimitive;
|
use num_enum::TryFromPrimitive;
|
||||||
use ops_sat_rs::config::cam_error::{self, CameraError};
|
use ops_sat_rs::config::cam_error::{self, CameraError};
|
||||||
use ops_sat_rs::config::GENERIC_FAILED;
|
use ops_sat_rs::config::{GENERIC_FAILED, HOME_PATH};
|
||||||
use ops_sat_rs::TimeStampHelper;
|
use ops_sat_rs::TimeStampHelper;
|
||||||
use satrs::action::{ActionRequest, ActionRequestVariant};
|
use satrs::action::{ActionRequest, ActionRequestVariant};
|
||||||
use satrs::hk::HkRequest;
|
use satrs::hk::HkRequest;
|
||||||
@ -40,8 +40,10 @@ use satrs::request::{GenericMessage, MessageMetadata, UniqueApidTargetId};
|
|||||||
use satrs::res_code::ResultU16;
|
use satrs::res_code::ResultU16;
|
||||||
use satrs::tmtc::PacketAsVec;
|
use satrs::tmtc::PacketAsVec;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::io::{self, Write};
|
||||||
use std::process::{Command, Output};
|
use std::process::{Command, Output};
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
const IMS_TESTAPP: &str = "ims100_testapp";
|
const IMS_TESTAPP: &str = "ims100_testapp";
|
||||||
|
|
||||||
@ -101,7 +103,7 @@ pub enum ActionId {
|
|||||||
|
|
||||||
// TODO what happens if limits are exceded
|
// TODO what happens if limits are exceded
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
#[derive(Debug, Serialize, Deserialize, new)]
|
#[derive(Debug, Clone, Serialize, Deserialize, new)]
|
||||||
pub struct CameraPictureParameters {
|
pub struct CameraPictureParameters {
|
||||||
pub R: u8,
|
pub R: u8,
|
||||||
pub G: u8,
|
pub G: u8,
|
||||||
@ -112,19 +114,61 @@ pub struct CameraPictureParameters {
|
|||||||
pub W: u32, // wait time between pictures in ms, max: 40000
|
pub W: u32, // wait time between pictures in ms, max: 40000
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
pub trait TakeImageExecutor {
|
||||||
pub struct Ims100BatchHandler {
|
fn take_image(&self, param: &CameraPictureParameters) -> io::Result<(Command, Output)>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct Ims100ImageExecutor {}
|
||||||
|
|
||||||
|
pub fn build_take_image_command(param: &CameraPictureParameters) -> Command {
|
||||||
|
let mut cmd = Command::new(IMS_TESTAPP);
|
||||||
|
cmd.arg("-R")
|
||||||
|
.arg(param.R.to_string())
|
||||||
|
.arg("-G")
|
||||||
|
.arg(param.G.to_string())
|
||||||
|
.arg("-B")
|
||||||
|
.arg(param.B.to_string())
|
||||||
|
.arg("-c")
|
||||||
|
.arg("/dev/cam_tty")
|
||||||
|
.arg("-m")
|
||||||
|
.arg("/dev/cam_sd")
|
||||||
|
.arg("-v")
|
||||||
|
.arg("0")
|
||||||
|
.arg("-n")
|
||||||
|
.arg(param.N.to_string());
|
||||||
|
if param.P {
|
||||||
|
cmd.arg("-p");
|
||||||
|
}
|
||||||
|
cmd.arg("-e")
|
||||||
|
.arg(param.E.to_string())
|
||||||
|
.arg("-w")
|
||||||
|
.arg(param.W.to_string());
|
||||||
|
cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TakeImageExecutor for Ims100ImageExecutor {
|
||||||
|
fn take_image(&self, param: &CameraPictureParameters) -> io::Result<(Command, Output)> {
|
||||||
|
let mut cmd = build_take_image_command(param);
|
||||||
|
info!("taking image with command: {cmd:?}");
|
||||||
|
let output = cmd.output()?;
|
||||||
|
Ok((cmd, output))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Ims100BatchHandler<ImgExecutor: TakeImageExecutor = Ims100ImageExecutor> {
|
||||||
id: UniqueApidTargetId,
|
id: UniqueApidTargetId,
|
||||||
|
image_executor: ImgExecutor,
|
||||||
composite_request_rx: mpsc::Receiver<GenericMessage<CompositeRequest>>,
|
composite_request_rx: mpsc::Receiver<GenericMessage<CompositeRequest>>,
|
||||||
tm_tx: mpsc::Sender<PacketAsVec>,
|
tm_tx: mpsc::Sender<PacketAsVec>,
|
||||||
action_reply_tx: mpsc::Sender<GenericMessage<ActionReplyPus>>,
|
action_reply_tx: mpsc::Sender<GenericMessage<ActionReplyPus>>,
|
||||||
stamp_helper: TimeStampHelper,
|
stamp_helper: TimeStampHelper,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
impl<ImgExecutor: TakeImageExecutor> Ims100BatchHandler<ImgExecutor> {
|
||||||
impl Ims100BatchHandler {
|
|
||||||
pub fn new(
|
pub fn new(
|
||||||
id: UniqueApidTargetId,
|
id: UniqueApidTargetId,
|
||||||
|
image_executor: ImgExecutor,
|
||||||
composite_request_rx: mpsc::Receiver<GenericMessage<CompositeRequest>>,
|
composite_request_rx: mpsc::Receiver<GenericMessage<CompositeRequest>>,
|
||||||
tm_tx: mpsc::Sender<PacketAsVec>,
|
tm_tx: mpsc::Sender<PacketAsVec>,
|
||||||
action_reply_tx: mpsc::Sender<GenericMessage<ActionReplyPus>>,
|
action_reply_tx: mpsc::Sender<GenericMessage<ActionReplyPus>>,
|
||||||
@ -132,6 +176,7 @@ impl Ims100BatchHandler {
|
|||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
id,
|
id,
|
||||||
|
image_executor,
|
||||||
composite_request_rx,
|
composite_request_rx,
|
||||||
tm_tx,
|
tm_tx,
|
||||||
action_reply_tx,
|
action_reply_tx,
|
||||||
@ -223,14 +268,17 @@ impl Ims100BatchHandler {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
match self.take_picture(¶m) {
|
match self.take_picture(¶m) {
|
||||||
Ok(ref output) => {
|
Ok((cmd, ref output)) => {
|
||||||
self.send_completion_success(requestor_info, action_request);
|
self.send_completion_success(requestor_info, action_request);
|
||||||
if let Err(e) =
|
if let Err(e) =
|
||||||
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)
|
||||||
{
|
{
|
||||||
log::error!("sending data reply unexpectedly failed: {e}");
|
log::error!("sending data reply unexpectedly failed: {e}");
|
||||||
}
|
}
|
||||||
self.create_metadata_file(¶m);
|
if let Err(e) = self.create_metadata_file(cmd, ¶m) {
|
||||||
|
// TODO: Generate event?
|
||||||
|
log::error!("issue creating metadata file: {e}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Err(e) => match e {
|
Err(e) => match e {
|
||||||
CameraError::TakeImageError(ref err_str) => {
|
CameraError::TakeImageError(ref err_str) => {
|
||||||
@ -262,8 +310,25 @@ impl Ims100BatchHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_metadata_file(&mut self, _param: &CameraPictureParameters) {
|
pub fn create_metadata_file(
|
||||||
// TODO: Implement
|
&mut self,
|
||||||
|
cmd: Command,
|
||||||
|
param: &CameraPictureParameters,
|
||||||
|
) -> io::Result<()> {
|
||||||
|
let now = SystemTime::now();
|
||||||
|
let unix_timestamp = now.duration_since(UNIX_EPOCH);
|
||||||
|
if unix_timestamp.is_err() {
|
||||||
|
log::error!("failed to get unix timestamp, time went backwards?");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
let unix_timestamp = unix_timestamp.unwrap().as_millis();
|
||||||
|
let mut metadata_path = HOME_PATH.get().unwrap().clone();
|
||||||
|
metadata_path.push(format!("img_msec_{}.txt", unix_timestamp));
|
||||||
|
let mut file = std::fs::File::create(metadata_path)?;
|
||||||
|
writeln!(file, "time: {}", humantime::format_rfc3339_seconds(now))?;
|
||||||
|
writeln!(file, "cmd params: {:?}", param)?;
|
||||||
|
writeln!(file, "cmd: {:?}", cmd)?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_completion_success(&self, requestor: &MessageMetadata, action_req: &ActionRequest) {
|
pub fn send_completion_success(&self, requestor: &MessageMetadata, action_req: &ActionRequest) {
|
||||||
@ -294,31 +359,11 @@ impl Ims100BatchHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn take_picture(&mut self, param: &CameraPictureParameters) -> Result<Output, CameraError> {
|
pub fn take_picture(
|
||||||
let mut cmd = Command::new(IMS_TESTAPP);
|
&mut self,
|
||||||
cmd.arg("-R")
|
param: &CameraPictureParameters,
|
||||||
.arg(param.R.to_string())
|
) -> Result<(Command, Output), CameraError> {
|
||||||
.arg("-G")
|
let (cmd, output) = self.image_executor.take_image(param)?;
|
||||||
.arg(param.G.to_string())
|
|
||||||
.arg("-B")
|
|
||||||
.arg(param.B.to_string())
|
|
||||||
.arg("-c")
|
|
||||||
.arg("/dev/cam_tty")
|
|
||||||
.arg("-m")
|
|
||||||
.arg("/dev/cam_sd")
|
|
||||||
.arg("-v")
|
|
||||||
.arg("0")
|
|
||||||
.arg("-n")
|
|
||||||
.arg(param.N.to_string());
|
|
||||||
if param.P {
|
|
||||||
cmd.arg("-p");
|
|
||||||
}
|
|
||||||
cmd.arg("-e")
|
|
||||||
.arg(param.E.to_string())
|
|
||||||
.arg("-w")
|
|
||||||
.arg(param.W.to_string());
|
|
||||||
info!("taking image with command: {cmd:?}");
|
|
||||||
let output = cmd.output()?;
|
|
||||||
|
|
||||||
info!("imager cmd status: {}", &output.status);
|
info!("imager cmd status: {}", &output.status);
|
||||||
info!("imager output: {}", String::from_utf8_lossy(&output.stdout));
|
info!("imager output: {}", String::from_utf8_lossy(&output.stdout));
|
||||||
@ -330,7 +375,7 @@ impl Ims100BatchHandler {
|
|||||||
if !output.status.success() {
|
if !output.status.success() {
|
||||||
return Err(CameraError::TakeImageError(error_string.to_string()));
|
return Err(CameraError::TakeImageError(error_string.to_string()));
|
||||||
}
|
}
|
||||||
Ok(output)
|
Ok((cmd, output))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
@ -349,6 +394,25 @@ impl Ims100BatchHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Ims100BatchHandler {
|
||||||
|
pub fn new_with_default_img_executor(
|
||||||
|
id: UniqueApidTargetId,
|
||||||
|
composite_request_rx: mpsc::Receiver<GenericMessage<CompositeRequest>>,
|
||||||
|
tm_tx: mpsc::Sender<PacketAsVec>,
|
||||||
|
action_reply_tx: mpsc::Sender<GenericMessage<ActionReplyPus>>,
|
||||||
|
stamp_helper: TimeStampHelper,
|
||||||
|
) -> Self {
|
||||||
|
Self::new(
|
||||||
|
id,
|
||||||
|
Ims100ImageExecutor::default(),
|
||||||
|
composite_request_rx,
|
||||||
|
tm_tx,
|
||||||
|
action_reply_tx,
|
||||||
|
stamp_helper,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::handlers::camera::{
|
use crate::handlers::camera::{
|
||||||
@ -356,35 +420,76 @@ mod tests {
|
|||||||
};
|
};
|
||||||
use crate::requests::CompositeRequest;
|
use crate::requests::CompositeRequest;
|
||||||
use ops_sat_rs::config::components::CAMERA_HANDLER;
|
use ops_sat_rs::config::components::CAMERA_HANDLER;
|
||||||
|
use ops_sat_rs::config::HOME_PATH;
|
||||||
use ops_sat_rs::TimeStampHelper;
|
use ops_sat_rs::TimeStampHelper;
|
||||||
use satrs::action::{ActionRequest, ActionRequestVariant};
|
use satrs::action::{ActionRequest, ActionRequestVariant};
|
||||||
use satrs::pus::action::ActionReplyPus;
|
use satrs::pus::action::{ActionReplyPus, ActionReplyVariant};
|
||||||
use satrs::request::{GenericMessage, MessageMetadata};
|
use satrs::request::{GenericMessage, MessageMetadata};
|
||||||
use satrs::tmtc::PacketAsVec;
|
use satrs::tmtc::PacketAsVec;
|
||||||
|
use satrs::ComponentId;
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
use std::os::unix::process::ExitStatusExt;
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
|
use tempfile::tempdir;
|
||||||
|
|
||||||
|
use super::{build_take_image_command, TakeImageExecutor};
|
||||||
|
|
||||||
|
const REQUESTOR_ID: ComponentId = 1;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct Ims100TestImageExecutor {
|
||||||
|
pub called_with_params: RefCell<VecDeque<CameraPictureParameters>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TakeImageExecutor for Ims100TestImageExecutor {
|
||||||
|
fn take_image(
|
||||||
|
&self,
|
||||||
|
param: &CameraPictureParameters,
|
||||||
|
) -> std::io::Result<(std::process::Command, std::process::Output)> {
|
||||||
|
let mut param_deque = self.called_with_params.borrow_mut();
|
||||||
|
param_deque.push_back(param.clone());
|
||||||
|
// We fake the test output, with no way to execute the actual command.
|
||||||
|
let output = std::process::Output {
|
||||||
|
status: std::process::ExitStatus::from_raw(0),
|
||||||
|
stdout: Vec::new(),
|
||||||
|
stderr: Vec::new(),
|
||||||
|
};
|
||||||
|
// We could generate the files as they are generated by the real batch handler.. But
|
||||||
|
// I think it's okay to verify that the function is called with the correct parameters
|
||||||
|
// and the metadata file is created for now.
|
||||||
|
Ok((build_take_image_command(param), output))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
struct Ims1000Testbench {
|
struct Ims100Testbench {
|
||||||
pub handler: Ims100BatchHandler,
|
pub handler: Ims100BatchHandler<Ims100TestImageExecutor>,
|
||||||
pub composite_req_tx: mpsc::Sender<GenericMessage<CompositeRequest>>,
|
pub composite_req_tx: mpsc::Sender<GenericMessage<CompositeRequest>>,
|
||||||
pub tm_receiver: mpsc::Receiver<PacketAsVec>,
|
pub tm_receiver: mpsc::Receiver<PacketAsVec>,
|
||||||
pub action_reply_rx: mpsc::Receiver<GenericMessage<ActionReplyPus>>,
|
pub action_reply_rx: mpsc::Receiver<GenericMessage<ActionReplyPus>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Ims1000Testbench {
|
impl Default for Ims100Testbench {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
|
// TODO: Set home path which is used inside the batch handler for creating metadata.
|
||||||
|
let temp_dir = tempdir().expect("errror creating temp directory");
|
||||||
|
HOME_PATH
|
||||||
|
.set(temp_dir.path().to_path_buf())
|
||||||
|
.expect("error setting test home path");
|
||||||
let (composite_request_tx, composite_request_rx) = mpsc::channel();
|
let (composite_request_tx, composite_request_rx) = mpsc::channel();
|
||||||
let (tm_tx, tm_rx) = mpsc::channel();
|
let (tm_tx, tm_rx) = mpsc::channel();
|
||||||
let (action_reply_tx, action_reply_rx) = mpsc::channel();
|
let (action_reply_tx, action_reply_rx) = mpsc::channel();
|
||||||
let time_helper = TimeStampHelper::default();
|
let time_helper = TimeStampHelper::default();
|
||||||
let cam_handler: Ims100BatchHandler = Ims100BatchHandler::new(
|
let cam_handler = Ims100BatchHandler::new(
|
||||||
CAMERA_HANDLER,
|
CAMERA_HANDLER,
|
||||||
|
Ims100TestImageExecutor::default(),
|
||||||
composite_request_rx,
|
composite_request_rx,
|
||||||
tm_tx,
|
tm_tx,
|
||||||
action_reply_tx,
|
action_reply_tx,
|
||||||
time_helper,
|
time_helper,
|
||||||
);
|
);
|
||||||
Ims1000Testbench {
|
Ims100Testbench {
|
||||||
handler: cam_handler,
|
handler: cam_handler,
|
||||||
composite_req_tx: composite_request_tx,
|
composite_req_tx: composite_request_tx,
|
||||||
tm_receiver: tm_rx,
|
tm_receiver: tm_rx,
|
||||||
@ -395,7 +500,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn command_line_execution() {
|
fn command_line_execution() {
|
||||||
let mut testbench = Ims1000Testbench::default();
|
let mut testbench = Ims100Testbench::default();
|
||||||
testbench
|
testbench
|
||||||
.handler
|
.handler
|
||||||
.take_picture(&DEFAULT_SINGLE_FLATSAT_CAM_PARAMS)
|
.take_picture(&DEFAULT_SINGLE_FLATSAT_CAM_PARAMS)
|
||||||
@ -411,8 +516,9 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_action_req() {
|
fn test_take_image_action_req() {
|
||||||
let mut testbench = Ims1000Testbench::default();
|
let request_id = 5;
|
||||||
|
let mut testbench = Ims100Testbench::default();
|
||||||
let data = serde_json::to_string(&DEFAULT_SINGLE_FLATSAT_CAM_PARAMS).unwrap();
|
let data = serde_json::to_string(&DEFAULT_SINGLE_FLATSAT_CAM_PARAMS).unwrap();
|
||||||
let req = ActionRequest::new(
|
let req = ActionRequest::new(
|
||||||
ActionId::CustomParameters as u32,
|
ActionId::CustomParameters as u32,
|
||||||
@ -421,12 +527,23 @@ mod tests {
|
|||||||
|
|
||||||
testbench
|
testbench
|
||||||
.handler
|
.handler
|
||||||
.handle_action_request(&MessageMetadata::new(1, 1), &req)
|
.handle_action_request(&MessageMetadata::new(request_id, REQUESTOR_ID), &req);
|
||||||
|
// TODO: Verify execution and generated metadata file.
|
||||||
|
let action_reply = testbench
|
||||||
|
.action_reply_rx
|
||||||
|
.try_recv()
|
||||||
|
.expect("expected action reply");
|
||||||
|
assert!(matches!(
|
||||||
|
action_reply.message.variant,
|
||||||
|
ActionReplyVariant::Completed
|
||||||
|
));
|
||||||
|
assert_eq!(action_reply.request_id(), request_id);
|
||||||
|
assert_eq!(action_reply.sender_id(), REQUESTOR_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_action_req_channel() {
|
fn test_action_req_channel() {
|
||||||
let mut testbench = Ims1000Testbench::default();
|
let mut testbench = Ims100Testbench::default();
|
||||||
|
|
||||||
let data = serde_json::to_string(&DEFAULT_SINGLE_FLATSAT_CAM_PARAMS).unwrap();
|
let data = serde_json::to_string(&DEFAULT_SINGLE_FLATSAT_CAM_PARAMS).unwrap();
|
||||||
let req = ActionRequest::new(
|
let req = ActionRequest::new(
|
||||||
|
26
src/main.rs
26
src/main.rs
@ -1,20 +1,21 @@
|
|||||||
use std::{
|
use std::{
|
||||||
env::temp_dir,
|
env::temp_dir,
|
||||||
net::{IpAddr, SocketAddr},
|
net::{IpAddr, SocketAddr},
|
||||||
path::PathBuf,
|
|
||||||
sync::{atomic::AtomicBool, mpsc, Arc},
|
sync::{atomic::AtomicBool, mpsc, Arc},
|
||||||
thread,
|
thread,
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
use log::info;
|
use log::info;
|
||||||
|
#[cfg(not(feature = "host"))]
|
||||||
|
use ops_sat_rs::config::HOME_PATH;
|
||||||
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},
|
||||||
pool::create_sched_tc_pool,
|
pool::create_sched_tc_pool,
|
||||||
set_up_ground_dir, set_up_low_prio_ground_dir,
|
set_up_ground_dir, set_up_home_path, set_up_low_prio_ground_dir,
|
||||||
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},
|
||||||
HOME_PATH, STOP_FILE_NAME, VALID_PACKET_ID_LIST, VERSION,
|
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::{components::CAMERA_HANDLER, tasks::FREQ_MS_EVENT_HANDLING};
|
||||||
use ops_sat_rs::config::{tasks::FREQ_MS_UDP_TMTC, OBSW_SERVER_ADDR, SERVER_PORT};
|
use ops_sat_rs::config::{tasks::FREQ_MS_UDP_TMTC, OBSW_SERVER_ADDR, SERVER_PORT};
|
||||||
@ -58,10 +59,18 @@ fn main() {
|
|||||||
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");
|
setup_logger().expect("setting up logging with fern failed");
|
||||||
set_up_low_prio_ground_dir();
|
|
||||||
set_up_ground_dir();
|
|
||||||
|
|
||||||
let app_cfg = create_app_config();
|
set_up_home_path();
|
||||||
|
#[cfg(feature = "host")]
|
||||||
|
let base_dir = std::env::current_dir()
|
||||||
|
.expect("getting current dir failed")
|
||||||
|
.to_path_buf();
|
||||||
|
#[cfg(not(feature = "host"))]
|
||||||
|
let base_dir = HOME_PATH.get().unwrap();
|
||||||
|
set_up_low_prio_ground_dir(base_dir.clone());
|
||||||
|
set_up_ground_dir(base_dir.clone());
|
||||||
|
|
||||||
|
let app_cfg = create_app_config(base_dir.clone());
|
||||||
info!("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));
|
||||||
@ -194,8 +203,7 @@ fn main() {
|
|||||||
stop_signal.clone(),
|
stop_signal.clone(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut home_path_stop_file = PathBuf::new();
|
let mut home_path_stop_file = base_dir.clone();
|
||||||
home_path_stop_file.push(HOME_PATH.as_path());
|
|
||||||
home_path_stop_file.push(STOP_FILE_NAME);
|
home_path_stop_file.push(STOP_FILE_NAME);
|
||||||
let mut tmp_path_stop_file = temp_dir();
|
let mut tmp_path_stop_file = temp_dir();
|
||||||
tmp_path_stop_file.push(STOP_FILE_NAME);
|
tmp_path_stop_file.push(STOP_FILE_NAME);
|
||||||
@ -217,7 +225,7 @@ fn main() {
|
|||||||
.expect("creating TCP SPP client failed");
|
.expect("creating TCP SPP client failed");
|
||||||
|
|
||||||
let timestamp_helper = TimeStampHelper::default();
|
let timestamp_helper = TimeStampHelper::default();
|
||||||
let mut camera_handler: Ims100BatchHandler = Ims100BatchHandler::new(
|
let mut camera_handler: Ims100BatchHandler = Ims100BatchHandler::new_with_default_img_executor(
|
||||||
CAMERA_HANDLER,
|
CAMERA_HANDLER,
|
||||||
camera_composite_rx,
|
camera_composite_rx,
|
||||||
tm_funnel_tx.clone(),
|
tm_funnel_tx.clone(),
|
||||||
|
Loading…
Reference in New Issue
Block a user