camera_merge_main #7

Merged
lkoester merged 9 commits from camera_merge_main into main 2024-04-19 11:45:25 +02:00
9 changed files with 310 additions and 9 deletions

13
Cargo.lock generated
View File

@ -488,6 +488,8 @@ dependencies = [
"num_enum",
"satrs",
"satrs-mib",
"serde",
"serde_json",
"strum",
"thiserror",
]
@ -675,6 +677,17 @@ dependencies = [
"syn 2.0.59",
]
[[package]]
name = "serde_json"
version = "1.0.116"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "smallvec"
version = "0.6.14"

View File

@ -15,6 +15,8 @@ strum = { version = "0.26", features = ["derive"] }
thiserror = "1"
derive-new = "0.6"
num_enum = "0.7"
serde = "1"
serde_json = "1"
mio = "0.8"
[dependencies.satrs]

View File

@ -233,13 +233,13 @@ def main():
raw_logger = RawTmtcTimedLogWrapper(when=TimedLogWhen.PER_HOUR, interval=1)
verificator = PusVerificator()
verification_wrapper = VerificationWrapper(verificator, _LOGGER, file_logger)
# Create primary TM handler and add it to the CCSDS Packet Handler
# Create primary TM handlers and add it to the CCSDS Packet Handler
tm_handler = PusHandler(file_logger, verification_wrapper, raw_logger)
ccsds_handler = CcsdsTmHandler(generic_handler=tm_handler)
# TODO: We could add the CFDP handler for the CFDP APID at a later stage.
# TODO: We could add the CFDP handlers for the CFDP APID at a later stage.
# ccsds_handler.add_apid_handler(tm_handler)
# Create TC handler
# Create TC handlers
seq_count_provider = PusFileSeqCountProvider()
tc_handler = TcHandler(seq_count_provider, verification_wrapper)
tmtccmd.setup(setup_args=setup_args)

284
src/handlers/camera.rs Normal file
View File

@ -0,0 +1,284 @@
/// Device handler implementation for the IMS-100 Imager used on the OPS-SAT mission.
///
/// from the [OPSSAT Experimenter Wiki](https://opssat1.esoc.esa.int/projects/experimenter-information/wiki/Camera_Introduction):
/// OPS-SAT has a BST IMS-100 Imager onboard for image acquisition. These RGGB images are 2048x1944px in size.
///
/// There are two ways of taking pictures, with the NMF or by using the camera API directly.
///
/// As the NMF method is already explained in the NMF documentation we will focus on triggering the camera API.
///
/// The camera is located on the -Z face of OPS-SAT
///
/// Mapping between camera and satellite frames:
/// cam body
/// +x -z
/// +y -x
/// +z +y
///
/// If you look onto Flatsat as in your picture coordinate system for camera it is
///
/// Z Z pointing inside Flatsat
/// x---> X
/// |
/// |
/// v Y
///
/// see also https://opssat1.esoc.esa.int/dmsf/files/6/view
use crate::requests::CompositeRequest;
use derive_new::new;
use log::debug;
use ops_sat_rs::TimeStampHelper;
use satrs::action::{ActionRequest, ActionRequestVariant};
use satrs::hk::HkRequest;
use satrs::request::{GenericMessage, MessageMetadata, UniqueApidTargetId};
use satrs::tmtc::PacketAsVec;
use serde::{Deserialize, Serialize};
use std::io::Error;
use std::process::Command;
use std::sync::mpsc;
const DEFAULT_SINGLE_CAM_PARAMS: CameraPictureParameters = CameraPictureParameters {
R: 8,
G: 8,
B: 8,
N: 1,
P: true,
E: 2,
W: 1000,
};
const BALANCED_SINGLE_CAM_PARAMS: CameraPictureParameters = CameraPictureParameters {
R: 13,
G: 7,
B: 8,
N: 1,
P: true,
E: 2,
W: 1000,
};
const DEFAULT_SINGLE_FLATSAT_CAM_PARAMS: CameraPictureParameters = CameraPictureParameters {
R: 8,
G: 8,
B: 8,
N: 1,
P: true,
E: 200,
W: 1000,
};
const BALANCED_SINGLE_FLATSAT_CAM_PARAMS: CameraPictureParameters = CameraPictureParameters {
R: 13,
G: 7,
B: 8,
N: 1,
P: true,
E: 200,
W: 1000,
};
#[derive(Debug)]
pub enum CameraActionIds {
DefaultSingle = 1,
BalancedSingle = 2,
DefaultSingleFlatSat = 3,
BalancedSingleFlatSat = 4,
CustomParameters = 5,
}
lkoester marked this conversation as resolved
Review

maybe those can be constants?

maybe those can be constants?
impl TryFrom<u32> for CameraActionIds {
type Error = ();
fn try_from(value: u32) -> Result<Self, Self::Error> {
match value {
value if value == CameraActionIds::DefaultSingle as u32 => {
Ok(CameraActionIds::DefaultSingle)
}
value if value == CameraActionIds::BalancedSingle as u32 => {
Ok(CameraActionIds::BalancedSingle)
}
value if value == CameraActionIds::DefaultSingleFlatSat as u32 => {
Ok(CameraActionIds::DefaultSingleFlatSat)
}
value if value == CameraActionIds::BalancedSingleFlatSat as u32 => {
Ok(CameraActionIds::BalancedSingleFlatSat)
}
value if value == CameraActionIds::CustomParameters as u32 => {
Ok(CameraActionIds::CustomParameters)
}
_ => Err(()),
}
}
}
// TODO what happens if limits are exceded
#[allow(non_snake_case)]
#[derive(Serialize, Deserialize, new)]
pub struct CameraPictureParameters {
pub R: u8,
pub G: u8,
pub B: u8,
pub N: u8, // number of images, max: 26
pub P: bool, // .png flag, true converts raw extracted image from camera to a png
pub E: u32, // exposure time in ms, max: 1580, default: 2, FlatSat: 200
pub W: u32, // wait time between pictures in ms, max: 40000
}
#[allow(dead_code)]
#[derive(new)]
pub struct IMS100BatchHandler {
id: UniqueApidTargetId,
// mode_interface: MpscModeLeafInterface,
composite_request_receiver: mpsc::Receiver<GenericMessage<CompositeRequest>>,
// hk_reply_sender: mpsc::Sender<GenericMessage<HkReply>>,
tm_sender: mpsc::Sender<PacketAsVec>,
stamp_helper: TimeStampHelper,
}
#[allow(non_snake_case)]
#[allow(dead_code)]
lkoester marked this conversation as resolved Outdated

I don't think we need to abstract the TM Sender. It will always be a mpsc::Sender<PacketAsVec>

I don't think we need to abstract the TM Sender. It will always be a `mpsc::Sender<PacketAsVec>`
impl IMS100BatchHandler {
pub fn periodic_operation(&mut self) {
self.stamp_helper.update_from_now();
// Handle requests.
self.handle_composite_requests();
// self.handle_mode_requests();
}
pub fn handle_composite_requests(&mut self) {
loop {
match self.composite_request_receiver.try_recv() {
Ok(ref msg) => match &msg.message {
CompositeRequest::Hk(hk_request) => {
self.handle_hk_request(&msg.requestor_info, hk_request);
}
CompositeRequest::Action(action_request) => {
self.handle_action_request(&msg.requestor_info, action_request);
}
},
Err(_) => {}
}
}
}
pub fn handle_hk_request(
&mut self,
_requestor_info: &MessageMetadata,
_hk_request: &HkRequest,
) {
// TODO add hk to opssat
}
pub fn handle_action_request(
&mut self,
_requestor_info: &MessageMetadata,
action_request: &ActionRequest,
) -> std::io::Result<()> {
let param = match CameraActionIds::try_from(action_request.action_id).unwrap() {
CameraActionIds::DefaultSingle => DEFAULT_SINGLE_CAM_PARAMS,
CameraActionIds::BalancedSingle => BALANCED_SINGLE_CAM_PARAMS,
CameraActionIds::DefaultSingleFlatSat => DEFAULT_SINGLE_FLATSAT_CAM_PARAMS,
CameraActionIds::BalancedSingleFlatSat => BALANCED_SINGLE_FLATSAT_CAM_PARAMS,
CameraActionIds::CustomParameters => match &action_request.variant {
ActionRequestVariant::NoData => return Err(Error::other("No Data sent!")),
ActionRequestVariant::StoreData(_) => {
// let param = serde_json::from_slice()
// TODO implement non dynamic version
return Err(Error::other(
"Static parameter transfer not implemented yet!",
));
}
ActionRequestVariant::VecData(data) => {
let param: serde_json::Result<CameraPictureParameters> =
serde_json::from_slice(data.as_slice());
match param {
Ok(param) => param,
Err(_) => {
return Err(Error::other("Unable to deserialize parameters"));
}
}
}
_ => return Err(Error::other("Invalid Action Request Variant!")),
},
};
self.take_picture(param)
}
pub fn take_picture(&mut self, param: CameraPictureParameters) -> std::io::Result<()> {
let mut cmd = Command::new("ims100_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());
let output = cmd.output()?;
debug!("{}", String::from_utf8_lossy(&output.stdout));
Ok(())
}
pub fn take_picture_from_str(
&mut self,
R: &str,
G: &str,
B: &str,
N: &str,
P: &str,
E: &str,
W: &str,
) -> std::io::Result<()> {
let mut cmd = Command::new("ims100_testapp");
cmd.arg("-R")
.arg(R)
.arg("-G")
.arg(G)
.arg("-B")
.arg(B)
.arg("-c")
.arg("/dev/cam_tty")
.arg("-m")
.arg("/dev/cam_sd")
.arg("-v")
.arg("0")
.arg("-n")
.arg(N)
.arg(P)
.arg("-e")
.arg(E)
.arg("-w")
.arg(W);
let output = cmd.output()?;
debug!("{}", String::from_utf8_lossy(&output.stdout));
Ok(())
}
}
#[cfg(test)]
mod tests {
#[test]
fn test_crc() {
// TODO
}
}

1
src/handlers/mod.rs Normal file
View File

@ -0,0 +1 @@
pub mod camera;

View File

@ -32,6 +32,7 @@ use crate::{
};
mod controller;
mod handlers;
mod interface;
mod logger;
mod pus;
@ -247,7 +248,7 @@ fn main() {
})
.unwrap();
info!("Starting PUS handler thread");
info!("Starting PUS handlers thread");
let pus_stop_signal = stop_signal.clone();
let jh_pus_handler = thread::Builder::new()
.name("ops-sat pus".to_string())
@ -277,5 +278,5 @@ fn main() {
.expect("Joining TM Funnel thread failed");
jh_pus_handler
.join()
.expect("Joining PUS handler thread failed");
.expect("Joining PUS handlers thread failed");
}

View File

@ -176,7 +176,7 @@ pub trait TargetedPusService {
fn check_for_request_timeouts(&mut self);
}
/// This is a generic handler class for all PUS services where a PUS telecommand is converted
/// This is a generic handlers class for all PUS services where a PUS telecommand is converted
/// to a targeted request.
///
/// The generic steps for this process are the following
@ -186,12 +186,12 @@ pub trait TargetedPusService {
/// 3. Convert the PUS TC to a typed request using the [PusTcToRequestConverter].
/// 4. Route the requests using the [GenericRequestRouter].
/// 5. Add the request to the active request map using the [ActiveRequestMapProvider] abstraction.
/// 6. Check for replies which complete the forwarded request. The handler takes care of
/// 6. Check for replies which complete the forwarded request. The handlers takes care of
/// the verification process.
/// 7. Check for timeouts of active requests. Generally, the timeout on the service level should
/// be highest expected timeout for the given target.
///
/// The handler exposes the following API:
/// The handlers exposes the following API:
///
/// 1. [Self::handle_one_tc] which tries to poll and handle one TC packet, covering steps 1-5.
/// 2. [Self::check_one_reply] which tries to poll and handle one reply, covering step 6.

View File

@ -49,7 +49,7 @@ impl TestCustomServiceWrapper {
pub fn poll_and_handle_next_packet(&mut self, time_stamp: &[u8]) -> HandlingStatus {
let res = self.handler.poll_and_handle_next_tc(time_stamp);
if res.is_err() {
warn!("PUS17 handler failed with error {:?}", res.unwrap_err());
warn!("PUS17 handlers failed with error {:?}", res.unwrap_err());
return HandlingStatus::Empty;
}
match res.unwrap() {

Binary file not shown.