Merge pull request 'camera_tests' (#13) from camera_tests into main

Reviewed-on: #13
This commit is contained in:
Robin Müller 2024-04-24 17:43:41 +02:00
commit 707843ec9f
13 changed files with 653 additions and 75 deletions

201
Cargo.lock generated
View File

@ -304,6 +304,95 @@ dependencies = [
"log",
]
[[package]]
name = "futures"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0"
dependencies = [
"futures-channel",
"futures-core",
"futures-executor",
"futures-io",
"futures-sink",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-channel"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
dependencies = [
"futures-core",
"futures-sink",
]
[[package]]
name = "futures-core"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
[[package]]
name = "futures-executor"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d"
dependencies = [
"futures-core",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-io"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
[[package]]
name = "futures-macro"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.60",
]
[[package]]
name = "futures-sink"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5"
[[package]]
name = "futures-task"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
[[package]]
name = "futures-util"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
dependencies = [
"futures-channel",
"futures-core",
"futures-io",
"futures-macro",
"futures-sink",
"futures-task",
"memchr",
"pin-project-lite",
"pin-utils",
"slab",
]
[[package]]
name = "hashbrown"
version = "0.14.3"
@ -326,6 +415,20 @@ version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
[[package]]
name = "homedir"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22074da8bba2ef26fc1737ae6c777b5baab5524c2dc403b5c6a76166766ccda5"
dependencies = [
"cfg-if",
"nix",
"serde",
"widestring",
"windows-sys 0.48.0",
"wmi",
]
[[package]]
name = "humantime"
version = "2.1.0"
@ -410,6 +513,15 @@ version = "2.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
[[package]]
name = "memoffset"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4"
dependencies = [
"autocfg",
]
[[package]]
name = "mio"
version = "0.8.11"
@ -422,6 +534,19 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "nix"
version = "0.26.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b"
dependencies = [
"bitflags",
"cfg-if",
"libc",
"memoffset",
"pin-utils",
]
[[package]]
name = "nodrop"
version = "0.1.14"
@ -482,6 +607,7 @@ dependencies = [
"derive-new",
"env_logger",
"fern",
"homedir",
"humantime",
"lazy_static",
"log",
@ -516,6 +642,18 @@ version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
[[package]]
name = "pin-project-lite"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02"
[[package]]
name = "pin-utils"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "proc-macro-crate"
version = "3.1.0"
@ -704,6 +842,15 @@ dependencies = [
"serde",
]
[[package]]
name = "slab"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
dependencies = [
"autocfg",
]
[[package]]
name = "smallvec"
version = "0.6.14"
@ -932,6 +1079,24 @@ version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
[[package]]
name = "widestring"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311"
[[package]]
name = "windows"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be"
dependencies = [
"windows-core",
"windows-implement",
"windows-interface",
"windows-targets 0.52.5",
]
[[package]]
name = "windows-core"
version = "0.52.0"
@ -941,6 +1106,28 @@ dependencies = [
"windows-targets 0.52.5",
]
[[package]]
name = "windows-implement"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12168c33176773b86799be25e2a2ba07c7aab9968b37541f1094dbd7a60c8946"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.60",
]
[[package]]
name = "windows-interface"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d8dc32e0095a7eeccebd0e3f09e9509365ecb3fc6ac4d6f5f14a3f6392942d1"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.60",
]
[[package]]
name = "windows-sys"
version = "0.48.0"
@ -1098,6 +1285,20 @@ dependencies = [
"memchr",
]
[[package]]
name = "wmi"
version = "0.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc2f0a4062ca522aad4705a2948fd4061b3857537990202a8ddd5af21607f79a"
dependencies = [
"chrono",
"futures",
"log",
"serde",
"thiserror",
"windows",
]
[[package]]
name = "zerocopy"
version = "0.7.32"

View File

@ -19,6 +19,7 @@ num_enum = "0.7"
serde = "1"
serde_json = "1"
mio = "0.8"
homedir = "0.2"
socket2 = "0.5"
[dependencies.satrs]

31
pytmtc/camera_params.py Normal file
View File

@ -0,0 +1,31 @@
import struct
from serde import Model, fields
from common import EXPERIMENT_APID, UniqueId, make_addressable_id
class CameraParameters(Model):
R: fields.Int()
G: fields.Int()
B: fields.Int()
N: fields.Int()
P: fields.Bool()
E: fields.Int()
W: fields.Int()
def serialize_for_uplink(self) -> bytearray:
return self.to_json().encode('utf-8')
# Example serialization
data = bytearray(make_addressable_id(EXPERIMENT_APID, UniqueId.CameraHandler))
params = CameraParameters(8, 8, 8, 1, True, 200, 1000)
serialized = params.to_json().encode('utf-8')
byte_string = bytearray(struct.pack('!{}s'.format(len(serialized)), serialized))
print(byte_string)
print(params.serialize_for_uplink())
data.extend(params.serialize_for_uplink())
print(data)
# Example deserialization
data = '{"R": 100, "G": 150, "B": 200, "N": 3, "P": true, "E": 10, "W": 20}'
deserialized_params = CameraParameters.from_json(data)
print(deserialized_params)

View File

@ -3,13 +3,30 @@ from __future__ import annotations
import dataclasses
import enum
import struct
from serde import Model, fields
EXPERIMENT_ID = 278
EXPERIMENT_APID = 1024 + EXPERIMENT_ID
class UniqueId(enum.IntEnum):
Controller = 0
PusEventManagement = 1
PusRouting = 2
PusTest = 3
PusAction = 4
PusMode = 5
PusHk = 6
UdpServer = 7
TcpServer = 8
TcpSppClient = 9
PusScheduler = 10
CameraHandler = 11
class EventSeverity(enum.IntEnum):
INFO = 0
LOW = 1
MEDIUM = 2
@ -43,6 +60,13 @@ class AcsHkIds(enum.IntEnum):
def make_addressable_id(target_id: int, unique_id: int) -> bytes:
byte_string = bytearray(struct.pack("!I", target_id))
byte_string.extend(struct.pack("!I", unique_id))
byte_string = bytearray(struct.pack("!I", unique_id))
# byte_string = bytearray(struct.pack("!I", target_id))
# byte_string.extend(struct.pack("!I", unique_id))
return byte_string
def make_addressable_id_with_action_id(unique_id: int, action_id: int) -> bytes:
byte_string = bytearray(struct.pack("!I", unique_id))
byte_string.extend(struct.pack("!I", action_id))
return byte_string

View File

@ -9,6 +9,10 @@ from tmtccmd.pus.tc.s200_fsfw_mode import Mode
from tmtccmd.tmtc import DefaultPusQueueHelper
from tmtccmd.pus.s11_tc_sched import create_time_tagged_cmd
from tmtccmd.pus.s200_fsfw_mode import Subservice as ModeSubservice
from serde import Model, fields
from camera_params import CameraParameters
from common import EXPERIMENT_APID, UniqueId, make_addressable_id, make_addressable_id_with_action_id
_LOGGER = logging.getLogger(__name__)
@ -65,6 +69,38 @@ def create_cmd_definition_tree() -> CmdTreeNode:
)
root_node.add_child(scheduler_node)
action_node = CmdTreeNode("action", "Action Node")
cam_node = CmdTreeNode(
"take_image", "Take Image with IMS Imager"
)
cam_node.add_child(
CmdTreeNode(
"default_single", "Default Single Image Camera Parameters"
)
)
cam_node.add_child(
CmdTreeNode(
"balanced_single", "Balanced Single Image Camera Parameters"
)
)
cam_node.add_child(
CmdTreeNode(
"default_single_flatsat", "Default Single Image Camera Parameters for use on FlatSat"
)
)
cam_node.add_child(
CmdTreeNode(
"balanced_single_flatsat", "Balanced Single Image Camera Parameters for use on FlatSat"
)
)
cam_node.add_child(
CmdTreeNode(
"custom_params", "Custom Camera Parameters as specified from file"
)
)
action_node.add_child(cam_node)
root_node.add_child(action_node)
return root_node
@ -97,6 +133,26 @@ def pack_pus_telecommands(q: DefaultPusQueueHelper, cmd_path: str):
)
if cmd_path_list[0] == "acs":
assert len(cmd_path_list) >= 2
if cmd_path_list[0] == "action":
assert len(cmd_path_list)>= 2
if cmd_path_list[1] == "take_image":
assert len(cmd_path_list)>= 3
q.add_log_cmd("Sending PUS take image action request with " + cmd_path_list[2] + " params.")
if cmd_path_list[2] == "default_single":
data = make_addressable_id_with_action_id(UniqueId.CameraHandler, 1)
if cmd_path_list[2] == "balanced_single":
data = make_addressable_id_with_action_id(UniqueId.CameraHandler, 2)
if cmd_path_list[2] == "default_single_flatsat":
data = make_addressable_id_with_action_id(UniqueId.CameraHandler, 3)
if cmd_path_list[2] == "balanced_single_flatsat":
data = make_addressable_id_with_action_id(UniqueId.CameraHandler, 4)
if cmd_path_list[2] == "custom":
data = make_addressable_id_with_action_id(UniqueId.CameraHandler, 5)
params = CameraParameters(8, 8, 8, 1, True, 200, 1000)
bytes = params.serialize_for_uplink()
data.extend(bytes)
print(data.hex(sep=","))
return q.add_pus_tc(PusTelecommand(service=8, subservice=128, apid=EXPERIMENT_APID, app_data=data))
def handle_set_mode_cmd(

View File

@ -147,6 +147,12 @@ class PusHandler(GenericApidHandlerBase):
_LOGGER.info(f"Received event packet. Event: {event_u32}")
if event_u32.group_id == 0 and event_u32.unique_id == 0:
_LOGGER.info("Received test event")
elif service == 8:
if pus_tm.subservice == 130:
_LOGGER.info(f"Received Action Data Reply TM[8,130]")
reply = pus_tm.source_data
reply = reply[6:]
_LOGGER.info(f"Data Reply Content: " + reply.decode('utf-8'))
elif service == 17:
tm_packet = Service17Tm.unpack(
packet, timestamp_len=CdsShortTimestamp.TIMESTAMP_SIZE

View File

@ -35,4 +35,12 @@ def tc_definitions() -> TmtcDefinitionWrapper:
info="PUS Service 11 TC Scheduling",
op_code_entry=srv_11,
)
srv_8 = OpCodeEntry()
srv_8.add("pic", "Action Request Image")
defs.add_service(
name=CoreServiceList.SERVICE_8,
info="PUS Service 8 Action",
op_code_entry=srv_8,
)
return defs

View File

@ -3,13 +3,12 @@ use num_enum::{IntoPrimitive, TryFromPrimitive};
use satrs::spacepackets::PacketId;
use satrs_mib::res_code::ResultU16Info;
use satrs_mib::resultcode;
use std::env;
use std::net::Ipv4Addr;
use std::path::{Path, PathBuf};
pub const STOP_FILE_NAME: &str = "stop-experiment";
pub const CONFIG_FILE_NAME: &str = "exp278.toml";
pub const HOME_FOLER_EXPERIMENT: &str = "/home/exp278";
pub const HOME_FOLDER_EXPERIMENT: &str = "/home/exp278";
pub const LOG_FOLDER: &str = "logs";
pub const OBSW_SERVER_ADDR: Ipv4Addr = Ipv4Addr::UNSPECIFIED;
@ -42,12 +41,17 @@ pub enum GroupId {
lazy_static! {
pub static ref HOME_PATH: PathBuf = {
let home_path_default = env::var("HOME").expect("HOME env variable not set");
let mut home_path = PathBuf::new();
home_path.push(if Path::new(HOME_FOLER_EXPERIMENT).exists() {
HOME_FOLER_EXPERIMENT
let home_path_default = homedir::get_my_home()
.expect("Getting home dir from OS failed.")
.expect("No home dir found.");
home_path.push(if Path::new(HOME_FOLDER_EXPERIMENT).exists() {
HOME_FOLDER_EXPERIMENT
} else {
&home_path_default
home_path_default
.to_str()
.expect("Error converting to string.")
});
home_path
};
@ -239,6 +243,7 @@ pub mod components {
TcpServer = 8,
TcpSppClient = 9,
PusScheduler = 10,
CameraHandler = 11,
}
pub const CONTROLLER_ID: UniqueApidTargetId =
@ -263,6 +268,8 @@ pub mod components {
UniqueApidTargetId::new(EXPERIMENT_APID, UniqueId::TcpServer as u32);
pub const TCP_SPP_CLIENT: UniqueApidTargetId =
UniqueApidTargetId::new(EXPERIMENT_APID, UniqueId::TcpSppClient as u32);
pub const CAMERA_HANDLER: UniqueApidTargetId =
UniqueApidTargetId::new(EXPERIMENT_APID, UniqueId::CameraHandler as u32);
}
pub mod tasks {

View File

@ -1,3 +1,4 @@
use crate::pus::action::send_data_reply;
/// 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):
@ -26,17 +27,23 @@
/// see also https://opssat1.esoc.esa.int/dmsf/files/6/view
use crate::requests::CompositeRequest;
use derive_new::new;
use log::debug;
use log::{debug, info};
use ops_sat_rs::TimeStampHelper;
use satrs::action::{ActionRequest, ActionRequestVariant};
use satrs::hk::HkRequest;
use satrs::pus::action::{ActionReplyPus, ActionReplyVariant};
use satrs::pus::EcssTmtcError;
use satrs::request::{GenericMessage, MessageMetadata, UniqueApidTargetId};
use satrs::tmtc::PacketAsVec;
use serde::{Deserialize, Serialize};
use std::io::Error;
use std::process::Command;
use std::fmt;
use std::fmt::Formatter;
use std::process::{Command, Output};
use std::sync::mpsc;
// const IMS_TESTAPP: &str = "scripts/ims100_testapp";
const IMS_TESTAPP: &str = "ims100_testapp";
const DEFAULT_SINGLE_CAM_PARAMS: CameraPictureParameters = CameraPictureParameters {
R: 8,
G: 8,
@ -77,8 +84,12 @@ const BALANCED_SINGLE_FLATSAT_CAM_PARAMS: CameraPictureParameters = CameraPictur
W: 1000,
};
// TODO copy as action
// TODO ls -l via cfdp
// TODO howto downlink
#[derive(Debug)]
pub enum CameraActionIds {
pub enum CameraActionId {
DefaultSingle = 1,
BalancedSingle = 2,
DefaultSingleFlatSat = 3,
@ -86,25 +97,25 @@ pub enum CameraActionIds {
CustomParameters = 5,
}
impl TryFrom<u32> for CameraActionIds {
impl TryFrom<u32> for CameraActionId {
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 == CameraActionId::DefaultSingle as u32 => {
Ok(CameraActionId::DefaultSingle)
}
value if value == CameraActionIds::BalancedSingle as u32 => {
Ok(CameraActionIds::BalancedSingle)
value if value == CameraActionId::BalancedSingle as u32 => {
Ok(CameraActionId::BalancedSingle)
}
value if value == CameraActionIds::DefaultSingleFlatSat as u32 => {
Ok(CameraActionIds::DefaultSingleFlatSat)
value if value == CameraActionId::DefaultSingleFlatSat as u32 => {
Ok(CameraActionId::DefaultSingleFlatSat)
}
value if value == CameraActionIds::BalancedSingleFlatSat as u32 => {
Ok(CameraActionIds::BalancedSingleFlatSat)
value if value == CameraActionId::BalancedSingleFlatSat as u32 => {
Ok(CameraActionId::BalancedSingleFlatSat)
}
value if value == CameraActionIds::CustomParameters as u32 => {
Ok(CameraActionIds::CustomParameters)
value if value == CameraActionId::CustomParameters as u32 => {
Ok(CameraActionId::CustomParameters)
}
_ => Err(()),
}
@ -113,7 +124,7 @@ impl TryFrom<u32> for CameraActionIds {
// TODO what happens if limits are exceded
#[allow(non_snake_case)]
#[derive(Serialize, Deserialize, new)]
#[derive(Debug, Serialize, Deserialize, new)]
pub struct CameraPictureParameters {
pub R: u8,
pub G: u8,
@ -124,20 +135,89 @@ pub struct CameraPictureParameters {
pub W: u32, // wait time between pictures in ms, max: 40000
}
#[derive(Debug)]
#[allow(dead_code)]
#[derive(new)]
pub enum CameraError {
TakeImageError,
NoDataSent,
VariantNotImplemented,
DeserializeError,
ListFileError,
IoError(std::io::Error),
EcssTmtcError(EcssTmtcError),
}
impl From<std::io::Error> for CameraError {
fn from(value: std::io::Error) -> Self {
Self::IoError(value)
}
}
impl From<EcssTmtcError> for CameraError {
fn from(value: EcssTmtcError) -> Self {
Self::EcssTmtcError(value)
}
}
impl fmt::Display for CameraError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
CameraError::TakeImageError => {
write!(f, "Error taking image.")
}
CameraError::NoDataSent => {
write!(f, "No data sent.")
}
CameraError::VariantNotImplemented => {
write!(f, "Request variant not implemented.")
}
CameraError::DeserializeError => {
write!(f, "Unable to deserialize parameters.")
}
CameraError::ListFileError => {
write!(f, "Error listing image files.")
}
CameraError::IoError(io_error) => {
write!(f, "{}", io_error)
}
CameraError::EcssTmtcError(ecss_tmtc_error) => {
write!(f, "{}", ecss_tmtc_error)
}
}
}
}
#[allow(dead_code)]
#[derive(Debug)]
pub struct IMS100BatchHandler {
id: UniqueApidTargetId,
// mode_interface: MpscModeLeafInterface,
composite_request_receiver: mpsc::Receiver<GenericMessage<CompositeRequest>>,
composite_request_rx: mpsc::Receiver<GenericMessage<CompositeRequest>>,
// hk_reply_sender: mpsc::Sender<GenericMessage<HkReply>>,
tm_sender: mpsc::Sender<PacketAsVec>,
tm_tx: mpsc::Sender<PacketAsVec>,
action_reply_tx: mpsc::Sender<GenericMessage<ActionReplyPus>>,
stamp_helper: TimeStampHelper,
}
#[allow(non_snake_case)]
#[allow(dead_code)]
impl IMS100BatchHandler {
pub fn new(
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 {
id,
composite_request_rx,
tm_tx,
action_reply_tx,
stamp_helper,
}
}
pub fn periodic_operation(&mut self) {
self.stamp_helper.update_from_now();
// Handle requests.
@ -147,7 +227,7 @@ impl IMS100BatchHandler {
pub fn handle_composite_requests(&mut self) {
loop {
match self.composite_request_receiver.try_recv() {
match self.composite_request_rx.try_recv() {
Ok(ref msg) => match &msg.message {
CompositeRequest::Hk(hk_request) => {
self.handle_hk_request(&msg.requestor_info, hk_request);
@ -181,41 +261,50 @@ impl IMS100BatchHandler {
pub fn handle_action_request(
&mut self,
_requestor_info: &MessageMetadata,
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"));
) -> Result<(), CameraError> {
let param =
match CameraActionId::try_from(action_request.action_id).expect("Invalid action id") {
CameraActionId::DefaultSingle => DEFAULT_SINGLE_CAM_PARAMS,
CameraActionId::BalancedSingle => BALANCED_SINGLE_CAM_PARAMS,
CameraActionId::DefaultSingleFlatSat => DEFAULT_SINGLE_FLATSAT_CAM_PARAMS,
CameraActionId::BalancedSingleFlatSat => BALANCED_SINGLE_FLATSAT_CAM_PARAMS,
CameraActionId::CustomParameters => match &action_request.variant {
ActionRequestVariant::NoData => return Err(CameraError::NoDataSent),
ActionRequestVariant::StoreData(_) => {
// let param = serde_json::from_slice()
// TODO implement non dynamic version
return Err(CameraError::VariantNotImplemented);
}
ActionRequestVariant::VecData(data) => {
let param: serde_json::Result<CameraPictureParameters> =
serde_json::from_slice(data.as_slice());
match param {
Ok(param) => param,
Err(_) => {
return Err(CameraError::DeserializeError);
}
}
}
}
_ => return Err(Error::other("Invalid Action Request Variant!")),
},
};
self.take_picture(param)
_ => return Err(CameraError::VariantNotImplemented),
},
};
let output = self.take_picture(param)?;
debug!("Sending action reply!");
send_data_reply(self.id, output.stdout, &self.stamp_helper, &self.tm_tx)?;
self.action_reply_tx
.send(GenericMessage::new(
*requestor_info,
ActionReplyPus::new(action_request.action_id, ActionReplyVariant::Completed),
))
.unwrap();
Ok(())
}
pub fn take_picture(&mut self, param: CameraPictureParameters) -> std::io::Result<()> {
let mut cmd = Command::new("ims100_testapp");
pub fn take_picture(&mut self, param: CameraPictureParameters) -> Result<Output, CameraError> {
info!("Taking image!");
let mut cmd = Command::new(IMS_TESTAPP);
cmd.arg("-R")
.arg(&param.R.to_string())
.arg("-G")
@ -237,12 +326,23 @@ impl IMS100BatchHandler {
.arg(&param.E.to_string())
.arg("-w")
.arg(&param.W.to_string());
let output = cmd.output()?;
debug!("{}", String::from_utf8_lossy(&output.stdout));
debug!("Imager Output: {}", String::from_utf8_lossy(&output.stdout));
Ok(())
Ok(output)
}
pub fn list_current_images(&self) -> Result<Vec<String>, CameraError> {
let output = Command::new("ls").arg("-l").arg("*.png").output()?;
if output.status.success() {
let output_str = String::from_utf8(output.stdout).unwrap();
let files: Vec<String> = output_str.lines().map(|s| s.to_string()).collect();
Ok(files)
} else {
Err(CameraError::ListFileError)
}
}
#[allow(clippy::too_many_arguments)]
@ -255,7 +355,7 @@ impl IMS100BatchHandler {
P: &str,
E: &str,
W: &str,
) -> std::io::Result<()> {
) -> Result<(), CameraError> {
let mut cmd = Command::new("ims100_testapp");
cmd.arg("-R")
.arg(R)
@ -287,8 +387,85 @@ impl IMS100BatchHandler {
#[cfg(test)]
mod tests {
use crate::handlers::camera::{
CameraActionId, CameraPictureParameters, IMS100BatchHandler,
DEFAULT_SINGLE_FLATSAT_CAM_PARAMS,
};
use crate::requests::CompositeRequest;
use ops_sat_rs::config::components::CAMERA_HANDLER;
use ops_sat_rs::TimeStampHelper;
use satrs::action::{ActionRequest, ActionRequestVariant};
use satrs::pus::action::ActionReplyPus;
use satrs::request::{GenericMessage, MessageMetadata};
use satrs::tmtc::PacketAsVec;
use std::sync::mpsc;
use std::sync::mpsc::{Receiver, Sender};
fn create_handler() -> (
IMS100BatchHandler,
Sender<GenericMessage<CompositeRequest>>,
Receiver<PacketAsVec>,
Receiver<GenericMessage<ActionReplyPus>>,
) {
let (composite_request_tx, composite_request_rx) = mpsc::channel();
let (tm_tx, tm_rx) = mpsc::channel();
let (action_reply_tx, action_reply_rx) = mpsc::channel();
let time_helper = TimeStampHelper::default();
let cam_handler: IMS100BatchHandler = IMS100BatchHandler::new(
CAMERA_HANDLER,
composite_request_rx,
tm_tx,
action_reply_tx,
time_helper,
);
(cam_handler, composite_request_tx, tm_rx, action_reply_rx)
}
#[test]
fn test_crc() {
// TODO
fn command_line_execution() {
let (mut cam_handler, req_tx, tm_rx, action_reply_rx) = create_handler();
cam_handler
.take_picture(DEFAULT_SINGLE_FLATSAT_CAM_PARAMS)
.unwrap();
}
#[test]
fn serialize_and_deserialize_command() {
let data = serde_json::to_string(&DEFAULT_SINGLE_FLATSAT_CAM_PARAMS).unwrap();
println!("{}", data);
let param: CameraPictureParameters = serde_json::from_str(&data).unwrap();
println!("{:?}", param);
}
#[test]
fn test_action_req() {
let (mut cam_handler, req_tx, tm_rx, action_reply_rx) = create_handler();
let data = serde_json::to_string(&DEFAULT_SINGLE_FLATSAT_CAM_PARAMS).unwrap();
let req = ActionRequest::new(
CameraActionId::CustomParameters as u32,
ActionRequestVariant::VecData(data.as_bytes().to_vec()),
);
cam_handler
.handle_action_request(&MessageMetadata::new(1, 1), &req)
.unwrap();
}
#[test]
fn test_action_req_channel() {
let (mut cam_handler, req_tx, tm_rx, action_reply_rx) = create_handler();
let data = serde_json::to_string(&DEFAULT_SINGLE_FLATSAT_CAM_PARAMS).unwrap();
let req = ActionRequest::new(
CameraActionId::CustomParameters as u32,
ActionRequestVariant::VecData(data.as_bytes().to_vec()),
);
let req = CompositeRequest::Action(req);
req_tx
.send(GenericMessage::new(MessageMetadata::new(1, 1), req))
.unwrap();
cam_handler.periodic_operation();
}
}

View File

@ -3,6 +3,7 @@ use satrs::spacepackets::time::TimeWriter;
pub mod config;
#[derive(Debug)]
pub struct TimeStampHelper {
stamper: CdsTime,
time_stamp: [u8; 7],

View File

@ -1,4 +1,4 @@
use std::path::Path;
use std::path::{Path, PathBuf};
use ops_sat_rs::config::LOG_FOLDER;
@ -6,6 +6,9 @@ pub fn setup_logger() -> Result<(), fern::InitError> {
if !Path::new(LOG_FOLDER).exists() && std::fs::create_dir_all(LOG_FOLDER).is_err() {
eprintln!("Failed to create log folder '{}'", LOG_FOLDER);
}
let mut path_buf = PathBuf::from(LOG_FOLDER);
path_buf.push("output.log");
println!("{:?}", path_buf);
fern::Dispatch::new()
.format(move |out, message, record| {
out.finish(format_args!(
@ -18,11 +21,7 @@ pub fn setup_logger() -> Result<(), fern::InitError> {
})
.level(log::LevelFilter::Debug)
.chain(std::io::stdout())
.chain(fern::log_file(format!(
"{}/output_{}.log",
LOG_FOLDER,
humantime::format_rfc3339_seconds(std::time::SystemTime::now())
))?)
.chain(fern::log_file(path_buf.as_os_str())?)
.apply()?;
Ok(())
}

View File

@ -6,6 +6,7 @@ use std::{
};
use log::info;
use ops_sat_rs::config::components::CAMERA_HANDLER;
use ops_sat_rs::config::{
cfg_file::create_app_config,
components::{CONTROLLER_ID, TCP_SERVER, TCP_SPP_CLIENT, UDP_SERVER},
@ -14,8 +15,10 @@ use ops_sat_rs::config::{
VALID_PACKET_ID_LIST,
};
use ops_sat_rs::config::{tasks::FREQ_MS_UDP_TMTC, OBSW_SERVER_ADDR, SERVER_PORT};
use ops_sat_rs::TimeStampHelper;
use satrs::hal::std::{tcp_server::ServerConfig, udp_server::UdpTcServer};
use crate::handlers::camera::IMS100BatchHandler;
use crate::pus::{
hk::create_hk_service, mode::create_mode_service, scheduler::create_scheduler_service,
PusTcDistributor, PusTcMpscRouter,
@ -67,12 +70,16 @@ fn main() {
let (_pus_mode_reply_tx, pus_mode_reply_rx) = mpsc::channel();
let (controller_composite_tx, controller_composite_rx) = mpsc::channel();
// let (controller_action_reply_tx, controller_action_reply_rx) = mpsc::channel();
let (camera_composite_tx, camera_composite_rx) = mpsc::channel();
// Some request are targetable. This map is used to retrieve sender handles based on a target ID.
let mut request_map = GenericRequestRouter::default();
request_map
.composite_router_map
.insert(CONTROLLER_ID.id(), controller_composite_tx);
request_map
.composite_router_map
.insert(CAMERA_HANDLER.id(), camera_composite_tx);
let pus_router = PusTcMpscRouter {
test_tc_sender: pus_test_tx,
@ -166,7 +173,7 @@ fn main() {
let mut controller = ExperimentController::new(
controller_composite_rx,
pus_action_reply_tx,
pus_action_reply_tx.clone(),
stop_signal.clone(),
);
@ -180,6 +187,18 @@ fn main() {
)
.expect("creating TCP SPP client failed");
let timestamp_helper = TimeStampHelper::default();
let mut camera_handler: IMS100BatchHandler = IMS100BatchHandler::new(
CAMERA_HANDLER,
camera_composite_rx,
tm_funnel_tx.clone(),
pus_action_reply_tx.clone(),
timestamp_helper,
);
// Main Task Thread Definitions
// Main Experiment Control Task
info!("Starting CTRL task");
let ctrl_stop_signal = stop_signal.clone();
let jh_ctrl_thread = thread::Builder::new()
@ -193,6 +212,7 @@ fn main() {
})
.unwrap();
// TMTC and UDP Task
info!("Starting TMTC and UDP task");
let tmtc_stop_signal = stop_signal.clone();
let jh_udp_tmtc = thread::Builder::new()
@ -210,6 +230,7 @@ fn main() {
})
.unwrap();
// TCP Server Task
let tcp_server_stop_signal = stop_signal.clone();
info!("Starting TCP server task");
let jh_tcp_server = thread::Builder::new()
@ -225,6 +246,7 @@ fn main() {
})
.unwrap();
// TCP SPP Client Task
// We could also move this to the existing TCP server thread, but we would have to adapt
// the server code for this so we do not block anymore and we pause manually if both the client
// and server are IDLE and have nothing to do..
@ -246,6 +268,7 @@ fn main() {
})
.unwrap();
// TM Funnel Task
info!("Starting TM funnel task");
let funnel_stop_signal = stop_signal.clone();
let jh_tm_funnel = thread::Builder::new()
@ -258,7 +281,8 @@ fn main() {
})
.unwrap();
info!("Starting PUS handlers thread");
// PUS Handler Task
info!("Starting PUS handlers task");
let pus_stop_signal = stop_signal.clone();
let jh_pus_handler = thread::Builder::new()
.name("ops-sat pus".to_string())
@ -271,6 +295,20 @@ fn main() {
})
.unwrap();
// Camera Handler Task
info!("Starting camera handler task");
let camera_stop_signal = stop_signal.clone();
let jh_camera_handler = thread::Builder::new()
.name("ops-sat camera".to_string())
.spawn(move || loop {
camera_handler.periodic_operation();
if camera_stop_signal.load(std::sync::atomic::Ordering::Relaxed) {
break;
}
})
.unwrap();
// Join Threads
jh_ctrl_thread
.join()
.expect("Joining Controller thread failed");
@ -289,4 +327,7 @@ fn main() {
jh_pus_handler
.join()
.expect("Joining PUS handlers thread failed");
jh_camera_handler
.join()
.expect("Joining camera handler thread failed");
}

View File

@ -1,6 +1,7 @@
use log::{error, warn};
use log::{debug, error, warn};
use ops_sat_rs::config::components::PUS_ACTION_SERVICE;
use ops_sat_rs::config::tmtc_err;
use ops_sat_rs::TimeStampHelper;
use satrs::action::{ActionRequest, ActionRequestVariant};
use satrs::params::WritableToBeBytes;
use satrs::pus::action::{
@ -13,11 +14,13 @@ use satrs::pus::verification::{
use satrs::pus::{
ActiveRequestProvider, EcssTcAndToken, EcssTcInVecConverter, EcssTmSender, EcssTmtcError,
GenericConversionError, PusPacketHandlerResult, PusReplyHandler, PusServiceHelper,
PusTcToRequestConverter,
PusTcToRequestConverter, PusTmVariant,
};
use satrs::request::{GenericMessage, UniqueApidTargetId};
use satrs::spacepackets::ecss::tc::PusTcReader;
use satrs::spacepackets::ecss::tm::{PusTmCreator, PusTmSecondaryHeader};
use satrs::spacepackets::ecss::{EcssEnumU16, PusPacket};
use satrs::spacepackets::SpHeader;
use satrs::tmtc::PacketAsVec;
use std::sync::mpsc;
use std::time::Duration;
@ -29,6 +32,8 @@ use super::{
PusTargetedRequestService, TargetedPusService,
};
pub const DATA_REPLY: u8 = 130;
pub struct ActionReplyHandler {
fail_data_buf: [u8; 128],
}
@ -270,6 +275,26 @@ impl TargetedPusService for ActionServiceWrapper {
}
}
pub fn send_data_reply<TmSender: EcssTmSender>(
apid_target: UniqueApidTargetId,
reply_data: Vec<u8>,
stamp_helper: &TimeStampHelper,
tm_sender: &TmSender,
) -> Result<(), EcssTmtcError> {
let sp_header = SpHeader::new_from_apid(apid_target.apid);
let sec_header = PusTmSecondaryHeader::new(8, DATA_REPLY, 0, 0, stamp_helper.stamp());
let mut data = Vec::new();
data.extend(apid_target.apid.to_be_bytes());
data.extend(apid_target.unique_id.to_be_bytes());
data.extend(reply_data);
debug!(
"{}",
String::from_utf8(data.clone()[6..].to_vec()).expect("Error decoding data reply.")
);
let data_reply_tm = PusTmCreator::new(sp_header, sec_header, &data, true);
tm_sender.send_tm(apid_target.id(), PusTmVariant::Direct(data_reply_tm))
}
#[cfg(test)]
mod tests {
use satrs::pus::test_util::{
@ -432,6 +457,7 @@ mod tests {
.send(EcssTcAndToken::new(
TcInMemory::Vec(PacketAsVec::new(
self.service.service_helper.id(),
//tc.to_vec().unwrap().into(),
tc.to_vec().unwrap(),
)),
accepted_token,