Compare commits
19 Commits
Author | SHA1 | Date | |
---|---|---|---|
14aa2f39a5 | |||
ff1fa9c8fa | |||
d86172c436 | |||
fab1859d78 | |||
ec69c7d581 | |||
c89db2e2d7 | |||
4cb9124ab7 | |||
a25b55baed | |||
2992829ccf | |||
f8f3bc73ac | |||
b4a84dbf20
|
|||
279fa42f31 | |||
eea2f76b9f
|
|||
c5eddcb292
|
|||
2566050b3b | |||
df556acbf5 | |||
eeba6fab44 | |||
cbdb017fe2 | |||
1e57b1f978
|
15
CHANGELOG.md
15
CHANGELOG.md
@ -6,6 +6,21 @@ All notable changes to this project will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
||||||
and this project adheres to [Semantic Versioning](http://semver.org/).
|
and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||||
|
|
||||||
|
# [v0.1.1] 2024-04-26
|
||||||
|
|
||||||
|
Various smaller improvements and tweaks.
|
||||||
|
|
||||||
|
## Fixed
|
||||||
|
|
||||||
|
- Logger file now has unique time-stamped name.
|
||||||
|
|
||||||
|
## Added
|
||||||
|
|
||||||
|
- Printout of SW version at startup.
|
||||||
|
- Setup of to ground directories.
|
||||||
|
- Camera handler commands: Image copying and shell command execution.
|
||||||
|
- host feature for testing on the development computer.
|
||||||
|
|
||||||
# [v0.1.0] 2024-04-24
|
# [v0.1.0] 2024-04-24
|
||||||
|
|
||||||
Initial release with PUS stack, TM sink, TC source, TMTC TCP/IP infrastructure
|
Initial release with PUS stack, TM sink, TC source, TMTC TCP/IP infrastructure
|
||||||
|
59
Cargo.lock
generated
59
Cargo.lock
generated
@ -113,6 +113,12 @@ version = "1.3.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "2.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bumpalo"
|
name = "bumpalo"
|
||||||
version = "3.16.0"
|
version = "3.16.0"
|
||||||
@ -295,6 +301,22 @@ version = "1.0.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "errno"
|
||||||
|
version = "0.3.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"windows-sys 0.52.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fastrand"
|
||||||
|
version = "2.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fern"
|
name = "fern"
|
||||||
version = "0.6.2"
|
version = "0.6.2"
|
||||||
@ -495,6 +517,12 @@ version = "0.2.153"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
|
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "linux-raw-sys"
|
||||||
|
version = "0.4.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.4.21"
|
version = "0.4.21"
|
||||||
@ -540,7 +568,7 @@ version = "0.26.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b"
|
checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags 1.3.2",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"libc",
|
"libc",
|
||||||
"memoffset",
|
"memoffset",
|
||||||
@ -613,12 +641,14 @@ dependencies = [
|
|||||||
"log",
|
"log",
|
||||||
"mio",
|
"mio",
|
||||||
"num_enum",
|
"num_enum",
|
||||||
|
"once_cell",
|
||||||
"satrs",
|
"satrs",
|
||||||
"satrs-mib",
|
"satrs-mib",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"socket2",
|
"socket2",
|
||||||
"strum",
|
"strum",
|
||||||
|
"tempfile",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"toml",
|
"toml",
|
||||||
]
|
]
|
||||||
@ -687,7 +717,7 @@ version = "0.4.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
|
checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags 1.3.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -719,6 +749,19 @@ version = "0.8.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56"
|
checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustix"
|
||||||
|
version = "0.38.34"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.5.0",
|
||||||
|
"errno",
|
||||||
|
"libc",
|
||||||
|
"linux-raw-sys",
|
||||||
|
"windows-sys 0.52.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustversion"
|
name = "rustversion"
|
||||||
version = "1.0.15"
|
version = "1.0.15"
|
||||||
@ -936,6 +979,18 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tempfile"
|
||||||
|
version = "3.10.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"fastrand",
|
||||||
|
"rustix",
|
||||||
|
"windows-sys 0.52.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "1.0.59"
|
version = "1.0.59"
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "ops-sat-rs"
|
name = "ops-sat-rs"
|
||||||
version = "0.1.0"
|
version = "0.1.1"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
@ -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"
|
||||||
@ -33,6 +34,10 @@ version = ">=0.1.2, <0.2"
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
env_logger = "0.11"
|
env_logger = "0.11"
|
||||||
|
tempfile = "3"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
host = []
|
||||||
|
|
||||||
# I don't think we need insane performance. If anything, a small binary is easier to upload
|
# I don't think we need insane performance. If anything, a small binary is easier to upload
|
||||||
# to the satellite.
|
# to the satellite.
|
||||||
|
@ -37,7 +37,7 @@ You can use the regular cargo workflow for this.
|
|||||||
### Running
|
### Running
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cargo run
|
cargo run --features host
|
||||||
```
|
```
|
||||||
|
|
||||||
### Testing
|
### Testing
|
||||||
|
@ -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(
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
||||||
|
use once_cell::sync::OnceCell;
|
||||||
use satrs::events::{EventU32TypedSev, SeverityInfo};
|
use satrs::events::{EventU32TypedSev, SeverityInfo};
|
||||||
use satrs::spacepackets::PacketId;
|
use satrs::spacepackets::PacketId;
|
||||||
use satrs_mib::res_code::ResultU16Info;
|
use satrs_mib::res_code::ResultU16Info;
|
||||||
@ -9,7 +10,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_NAME: &str = "toGround";
|
||||||
|
pub const TO_GROUND_LP_FOLDER_NAME: &str = "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;
|
||||||
@ -25,6 +28,9 @@ 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 static TO_GROUND_FOLDER_DIR: OnceCell<PathBuf> = OnceCell::new();
|
||||||
|
pub static TO_GROUND_LP_FOLDER_DIR: OnceCell<PathBuf> = OnceCell::new();
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, TryFromPrimitive, IntoPrimitive)]
|
#[derive(Copy, Clone, PartialEq, Eq, Debug, TryFromPrimitive, IntoPrimitive)]
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
pub enum CustomPusServiceId {
|
pub enum CustomPusServiceId {
|
||||||
@ -38,6 +44,7 @@ pub enum GroupId {
|
|||||||
Hk = 1,
|
Hk = 1,
|
||||||
Mode = 2,
|
Mode = 2,
|
||||||
Action = 3,
|
Action = 3,
|
||||||
|
Controller = 4,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const TEST_EVENT: EventU32TypedSev<SeverityInfo> =
|
pub const TEST_EVENT: EventU32TypedSev<SeverityInfo> =
|
||||||
@ -63,6 +70,49 @@ lazy_static! {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_up_low_prio_ground_dir() {
|
||||||
|
#[cfg(feature = "host")]
|
||||||
|
let mut to_ground_lp_dir = std::env::current_dir().expect("getting current dir failed");
|
||||||
|
#[cfg(not(feature = "host"))]
|
||||||
|
let mut to_ground_lp_dir = HOME_PATH.clone();
|
||||||
|
to_ground_lp_dir.push(TO_GROUND_LP_FOLDER_NAME);
|
||||||
|
if !Path::new(&to_ground_lp_dir).exists() {
|
||||||
|
log::info!(
|
||||||
|
"creating low priority to ground directory at {:?}",
|
||||||
|
to_ground_lp_dir
|
||||||
|
);
|
||||||
|
if std::fs::create_dir_all(&to_ground_lp_dir).is_err() {
|
||||||
|
log::error!(
|
||||||
|
"Failed to create low priority to ground directory '{:?}'",
|
||||||
|
to_ground_lp_dir
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TO_GROUND_LP_FOLDER_DIR
|
||||||
|
.set(to_ground_lp_dir)
|
||||||
|
.expect("attemting to set once cell twice");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_up_ground_dir() {
|
||||||
|
#[cfg(feature = "host")]
|
||||||
|
let mut to_ground_dir = std::env::current_dir().expect("getting current dir failed");
|
||||||
|
#[cfg(not(feature = "host"))]
|
||||||
|
let mut to_ground_dir = HOME_PATH.clone();
|
||||||
|
to_ground_dir.push(TO_GROUND_FOLDER_NAME);
|
||||||
|
if !Path::new(&to_ground_dir).exists() {
|
||||||
|
log::info!("creating to ground directory at {:?}", to_ground_dir);
|
||||||
|
if std::fs::create_dir_all(&to_ground_dir).is_err() {
|
||||||
|
log::error!(
|
||||||
|
"Failed to create low priority to ground directory '{:?}'",
|
||||||
|
to_ground_dir
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TO_GROUND_FOLDER_DIR
|
||||||
|
.set(to_ground_dir)
|
||||||
|
.expect("attemting to set once cell twice");
|
||||||
|
}
|
||||||
|
|
||||||
pub mod cfg_file {
|
pub mod cfg_file {
|
||||||
use std::{
|
use std::{
|
||||||
fs::File,
|
fs::File,
|
||||||
@ -212,6 +262,34 @@ pub mod mode_err {
|
|||||||
pub const WRONG_MODE: ResultU16 = ResultU16::new(GroupId::Mode as u8, 0);
|
pub const WRONG_MODE: ResultU16 = ResultU16::new(GroupId::Mode as u8, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub mod ctrl_err {
|
||||||
|
use super::*;
|
||||||
|
use satrs::res_code::ResultU16;
|
||||||
|
|
||||||
|
#[resultcode]
|
||||||
|
pub const INVALID_CMD_FORMAT: ResultU16 = ResultU16::new(GroupId::Controller as u8, 0);
|
||||||
|
#[resultcode]
|
||||||
|
pub const SHELL_CMD_IO_ERROR: ResultU16 = ResultU16::new(GroupId::Controller as u8, 1);
|
||||||
|
#[resultcode]
|
||||||
|
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,
|
||||||
|
SHELL_CMD_IO_ERROR_EXT,
|
||||||
|
SHELL_CMD_EXECUTION_FAILURE_EXT,
|
||||||
|
SHELL_CMD_INVALID_FORMAT_EXT,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
pub mod pool {
|
pub mod pool {
|
||||||
use satrs::pool::{StaticMemoryPool, StaticPoolConfig};
|
use satrs::pool::{StaticMemoryPool, StaticPoolConfig};
|
||||||
|
|
||||||
|
@ -1,31 +1,81 @@
|
|||||||
|
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};
|
||||||
|
use ops_sat_rs::config::{
|
||||||
|
HOME_PATH, STOP_FILE_NAME, TO_GROUND_FOLDER_DIR, TO_GROUND_LP_FOLDER_DIR,
|
||||||
|
};
|
||||||
|
use satrs::action::ActionRequestVariant;
|
||||||
use satrs::{
|
use satrs::{
|
||||||
action::ActionRequest,
|
action::ActionRequest,
|
||||||
|
params::Params,
|
||||||
pus::action::{ActionReplyPus, ActionReplyVariant},
|
pus::action::{ActionReplyPus, ActionReplyVariant},
|
||||||
request::{GenericMessage, MessageMetadata},
|
request::{GenericMessage, MessageMetadata},
|
||||||
|
res_code::ResultU16,
|
||||||
};
|
};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::env::temp_dir;
|
||||||
use std::{
|
use std::{
|
||||||
env::temp_dir,
|
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
|
process::Command,
|
||||||
sync::{atomic::AtomicBool, mpsc, Arc},
|
sync::{atomic::AtomicBool, mpsc, Arc},
|
||||||
};
|
};
|
||||||
|
|
||||||
use ops_sat_rs::config::{action_err::INVALID_ACTION_ID, HOME_PATH, STOP_FILE_NAME};
|
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,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::requests::CompositeRequest;
|
use crate::requests::CompositeRequest;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct ShellCmd<'a> {
|
||||||
|
cmd: &'a str,
|
||||||
|
args: Vec<&'a str>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, TryFromPrimitive)]
|
#[derive(Debug, Clone, Copy, TryFromPrimitive)]
|
||||||
#[repr(u32)]
|
#[repr(u32)]
|
||||||
pub enum ActionId {
|
pub enum ActionId {
|
||||||
StopExperiment = 1,
|
StopExperiment = 1,
|
||||||
|
DownlinkLogfile = 2,
|
||||||
|
DownlinkImages = 3,
|
||||||
|
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: TO_GROUND_FOLDER_DIR
|
||||||
|
.get()
|
||||||
|
.expect("to ground directory not set")
|
||||||
|
.clone(),
|
||||||
|
to_ground_low_prio_dir: TO_GROUND_LP_FOLDER_DIR
|
||||||
|
.get()
|
||||||
|
.expect("to ground low prio directory not set")
|
||||||
|
.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
pub struct ExperimentController {
|
pub struct ExperimentController {
|
||||||
pub composite_request_rx: mpsc::Receiver<GenericMessage<CompositeRequest>>,
|
pub composite_request_rx: mpsc::Receiver<GenericMessage<CompositeRequest>>,
|
||||||
pub action_reply_tx: mpsc::Sender<GenericMessage<ActionReplyPus>>,
|
pub action_reply_tx: mpsc::Sender<GenericMessage<ActionReplyPus>>,
|
||||||
pub stop_signal: Arc<AtomicBool>,
|
pub stop_signal: Arc<AtomicBool>,
|
||||||
home_path_stop_file: PathBuf,
|
pub paths: ControllerPathCollection,
|
||||||
tmp_path_stop_file: PathBuf,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ExperimentController {
|
impl ExperimentController {
|
||||||
@ -33,18 +83,13 @@ impl ExperimentController {
|
|||||||
composite_request_rx: mpsc::Receiver<GenericMessage<CompositeRequest>>,
|
composite_request_rx: mpsc::Receiver<GenericMessage<CompositeRequest>>,
|
||||||
action_reply_tx: mpsc::Sender<GenericMessage<ActionReplyPus>>,
|
action_reply_tx: mpsc::Sender<GenericMessage<ActionReplyPus>>,
|
||||||
stop_signal: Arc<AtomicBool>,
|
stop_signal: Arc<AtomicBool>,
|
||||||
|
paths: ControllerPathCollection,
|
||||||
) -> Self {
|
) -> 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 {
|
Self {
|
||||||
composite_request_rx,
|
composite_request_rx,
|
||||||
action_reply_tx,
|
action_reply_tx,
|
||||||
stop_signal,
|
stop_signal,
|
||||||
home_path_stop_file,
|
paths,
|
||||||
tmp_path_stop_file,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -70,35 +115,181 @@ impl ExperimentController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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 action_id = ActionId::try_from(action_req.action_id);
|
let send_completion_failure = |error_code: ResultU16, params: Option<Params>| {
|
||||||
if action_id.is_err() {
|
|
||||||
let result = self.action_reply_tx.send(GenericMessage::new_action_reply(
|
let result = self.action_reply_tx.send(GenericMessage::new_action_reply(
|
||||||
requestor,
|
requestor,
|
||||||
action_req.action_id,
|
action_req.action_id,
|
||||||
ActionReplyVariant::CompletionFailed {
|
ActionReplyVariant::CompletionFailed { error_code, params },
|
||||||
error_code: INVALID_ACTION_ID,
|
|
||||||
params: None,
|
|
||||||
},
|
|
||||||
));
|
));
|
||||||
if result.is_err() {
|
if result.is_err() {
|
||||||
log::error!("sending action reply failed");
|
log::error!("sending action reply failed");
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
let action_id = ActionId::try_from(action_req.action_id);
|
||||||
|
if action_id.is_err() {
|
||||||
|
send_completion_failure(INVALID_ACTION_ID, None);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let action_id = action_id.unwrap();
|
match action_id.unwrap() {
|
||||||
match action_id {
|
|
||||||
ActionId::StopExperiment => {
|
ActionId::StopExperiment => {
|
||||||
self.stop_signal
|
self.stop_signal
|
||||||
.store(true, std::sync::atomic::Ordering::Relaxed);
|
.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||||
let result = self.action_reply_tx.send(GenericMessage::new_action_reply(
|
self.send_completion_success(&requestor, &action_req);
|
||||||
|
}
|
||||||
|
ActionId::ExecuteShellCommandBlocking => {
|
||||||
|
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 => 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,
|
requestor,
|
||||||
action_req.action_id,
|
action_req,
|
||||||
ActionReplyVariant::Completed,
|
IMAGE_NOT_FOUND_FOR_COPY,
|
||||||
));
|
Some(e.to_string().into()),
|
||||||
if result.is_err() {
|
);
|
||||||
log::error!("sending action reply failed");
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_downlink_logfile(&self, requestor: &MessageMetadata, action_req: &ActionRequest) {
|
||||||
|
log::info!("copying logfile into {:?}", self.paths.to_ground_dir);
|
||||||
|
if let Some(logfile_path) = LOGFILE_PATH.get() {
|
||||||
|
self.handle_file_copy(
|
||||||
|
requestor,
|
||||||
|
action_req,
|
||||||
|
logfile_path,
|
||||||
|
&self.paths.to_ground_dir,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
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,
|
||||||
|
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_shell_command_execution(
|
||||||
|
&self,
|
||||||
|
requestor: &MessageMetadata,
|
||||||
|
action_req: &ActionRequest,
|
||||||
|
) {
|
||||||
|
if let ActionRequestVariant::VecData(data) = &action_req.variant {
|
||||||
|
let shell_cmd_result: serde_json::Result<ShellCmd> = serde_json::from_slice(data);
|
||||||
|
match shell_cmd_result {
|
||||||
|
Ok(shell_cmd) => {
|
||||||
|
log::info!("executing shell cmd {:?}", shell_cmd);
|
||||||
|
match Command::new(shell_cmd.cmd).args(shell_cmd.args).status() {
|
||||||
|
Ok(status) => {
|
||||||
|
if status.success() {
|
||||||
|
self.send_completion_success(requestor, action_req);
|
||||||
|
} else {
|
||||||
|
log::warn!("execution of command failed: {}", status);
|
||||||
|
self.send_completion_failure(
|
||||||
|
requestor,
|
||||||
|
action_req,
|
||||||
|
SHELL_CMD_EXECUTION_FAILURE,
|
||||||
|
Some(status.to_string().into()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!("execution of command failed with IO error: {}", e);
|
||||||
|
self.send_completion_failure(
|
||||||
|
requestor,
|
||||||
|
action_req,
|
||||||
|
SHELL_CMD_IO_ERROR,
|
||||||
|
Some(e.to_string().into()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(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 {
|
||||||
|
log::warn!("no shell command was supplied for shell command action command");
|
||||||
|
self.send_completion_failure(requestor, action_req, SHELL_CMD_INVALID_FORMAT, None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,7 +313,195 @@ impl ExperimentController {
|
|||||||
.store(true, std::sync::atomic::Ordering::Relaxed);
|
.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
check_at_path(self.tmp_path_stop_file.as_path());
|
check_at_path(self.paths.stop_file_tmp_path.as_path());
|
||||||
check_at_path(self.home_path_stop_file.as_path());
|
check_at_path(self.paths.stop_file_home_path.as_path());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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)]
|
||||||
|
mod tests {
|
||||||
|
use std::sync::{mpsc, Arc};
|
||||||
|
|
||||||
|
use tempfile::NamedTempFile;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn init() {
|
||||||
|
env_logger::builder().is_test(true).init();
|
||||||
|
}
|
||||||
|
pub struct ControllerTestbench {
|
||||||
|
pub composite_req_tx: mpsc::Sender<GenericMessage<CompositeRequest>>,
|
||||||
|
pub action_reply_rx: mpsc::Receiver<GenericMessage<ActionReplyPus>>,
|
||||||
|
pub stop_signal: Arc<AtomicBool>,
|
||||||
|
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() {
|
||||||
|
let mut testbench = ControllerTestbench::new();
|
||||||
|
let named_temp_file = NamedTempFile::new().expect("creating temp file failed");
|
||||||
|
let args = vec![named_temp_file
|
||||||
|
.path()
|
||||||
|
.to_str()
|
||||||
|
.expect("converting path to str failed")];
|
||||||
|
|
||||||
|
let cmd = ShellCmd { cmd: "rm", args };
|
||||||
|
let cmd_serialized = serde_json::to_string(&cmd).expect("serialization failed");
|
||||||
|
let action_req = satrs::action::ActionRequest {
|
||||||
|
action_id: ActionId::ExecuteShellCommandBlocking as u32,
|
||||||
|
variant: satrs::action::ActionRequestVariant::VecData(cmd_serialized.into_bytes()),
|
||||||
|
};
|
||||||
|
testbench
|
||||||
|
.composite_req_tx
|
||||||
|
.send(GenericMessage::new(
|
||||||
|
MessageMetadata::new(1, 2),
|
||||||
|
CompositeRequest::Action(action_req),
|
||||||
|
))
|
||||||
|
.expect("sending action request failed");
|
||||||
|
testbench.ctrl.perform_operation();
|
||||||
|
assert!(!named_temp_file.path().exists());
|
||||||
|
let action_reply = testbench
|
||||||
|
.action_reply_rx
|
||||||
|
.try_recv()
|
||||||
|
.expect("receiving action reply failed");
|
||||||
|
assert_eq!(
|
||||||
|
action_reply.message.action_id,
|
||||||
|
ActionId::ExecuteShellCommandBlocking as u32
|
||||||
|
);
|
||||||
|
match action_reply.message.variant {
|
||||||
|
ActionReplyVariant::Completed => (),
|
||||||
|
_ => {
|
||||||
|
panic!(
|
||||||
|
"unexecpted action reply variant {:?}",
|
||||||
|
action_reply.message.variant
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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<Hook: ActionHelperHook> {
|
||||||
|
pub requestor: MessageMetadata,
|
||||||
|
pub action_id: satrs::action::ActionId,
|
||||||
|
pub user_hook: Hook,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Hook: ActionHelperHook> ActionHelper<Hook> {
|
||||||
|
fn new(
|
||||||
|
&mut self,
|
||||||
|
requestor: MessageMetadata,
|
||||||
|
action_id: satrs::action::ActionId,
|
||||||
|
) -> Result<Option<Self>, 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<Params>,
|
||||||
|
) -> Result<(), GenericSendError> {
|
||||||
|
self.user_hook.send_reply(GenericMessage::new_action_reply(
|
||||||
|
self.requestor,
|
||||||
|
self.action_id,
|
||||||
|
ActionReplyVariant::CompletionFailed { error_code, params },
|
||||||
|
))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
@ -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!(
|
||||||
|
19
src/main.rs
19
src/main.rs
@ -1,5 +1,7 @@
|
|||||||
use std::{
|
use std::{
|
||||||
|
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,
|
||||||
@ -10,8 +12,9 @@ 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,
|
||||||
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,
|
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::{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};
|
||||||
@ -21,7 +24,7 @@ use satrs::{
|
|||||||
pus::event_man::EventRequestWithToken,
|
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::{controller::ExperimentController, pus::test::create_test_service};
|
||||||
use crate::{
|
use crate::{
|
||||||
events::EventHandler,
|
events::EventHandler,
|
||||||
@ -52,12 +55,14 @@ 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");
|
||||||
|
set_up_low_prio_ground_dir();
|
||||||
|
set_up_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));
|
||||||
|
|
||||||
@ -183,10 +188,16 @@ fn main() {
|
|||||||
stop_signal.clone(),
|
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(
|
let mut controller = ExperimentController::new(
|
||||||
controller_composite_rx,
|
controller_composite_rx,
|
||||||
pus_action_reply_tx.clone(),
|
pus_action_reply_tx.clone(),
|
||||||
stop_signal.clone(),
|
stop_signal.clone(),
|
||||||
|
ControllerPathCollection::default(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut tcp_spp_client = TcpSppClientStd::new(
|
let mut tcp_spp_client = TcpSppClientStd::new(
|
||||||
|
Reference in New Issue
Block a user