introduce mode commands to python client
All checks were successful
Rust/sat-rs/pipeline/pr-main This commit looks good

This commit is contained in:
Robin Müller 2024-04-03 14:55:23 +02:00
parent 575003db94
commit 68908f53d4
Signed by: muellerr
GPG Key ID: A649FB78196E3849
4 changed files with 87 additions and 28 deletions

View File

@ -22,6 +22,25 @@ def create_cmd_definition_tree() -> CmdTreeNode:
root_node = CmdTreeNode.root_node() root_node = CmdTreeNode.root_node()
hk_node = CmdTreeNode("hk", "Housekeeping Node", hide_children_for_print=True)
hk_node.add_child(CmdTreeNode("one_shot_hk", "Request One Shot HK set"))
hk_node.add_child(
CmdTreeNode("enable", "Enable periodic housekeeping data generation")
)
hk_node.add_child(
CmdTreeNode("disable", "Disable periodic housekeeping data generation")
)
mode_node = CmdTreeNode("mode", "Mode Node", hide_children_for_print=True)
set_mode_node = CmdTreeNode(
"set_mode", "Set Node", hide_children_which_are_leaves=True
)
set_mode_node.add_child(CmdTreeNode("off", "Set OFF Mode"))
set_mode_node.add_child(CmdTreeNode("on", "Set ON Mode"))
set_mode_node.add_child(CmdTreeNode("normal", "Set NORMAL Mode"))
mode_node.add_child(set_mode_node)
mode_node.add_child(CmdTreeNode("read_mode", "Read Mode"))
test_node = CmdTreeNode("test", "Test Node") test_node = CmdTreeNode("test", "Test Node")
test_node.add_child(CmdTreeNode("ping", "Send PUS ping TC")) test_node.add_child(CmdTreeNode("ping", "Send PUS ping TC"))
test_node.add_child(CmdTreeNode("trigger_event", "Send PUS test to trigger event")) test_node.add_child(CmdTreeNode("trigger_event", "Send PUS test to trigger event"))
@ -37,7 +56,9 @@ def create_cmd_definition_tree() -> CmdTreeNode:
acs_node = CmdTreeNode("acs", "ACS Subsystem Node") acs_node = CmdTreeNode("acs", "ACS Subsystem Node")
mgm_node = CmdTreeNode("mgms", "MGM devices node") mgm_node = CmdTreeNode("mgms", "MGM devices node")
mgm_node.add_child(CmdTreeNode("one_shot_hk", "Request one shot HK")) mgm_node.add_child(mode_node)
mgm_node.add_child(hk_node)
acs_node.add_child(mgm_node) acs_node.add_child(mgm_node)
root_node.add_child(acs_node) root_node.add_child(acs_node)

View File

@ -4,7 +4,7 @@ use satrs::queue::{GenericSendError, GenericTargetedMessagingError};
use satrs::spacepackets::ecss::hk; use satrs::spacepackets::ecss::hk;
use satrs::spacepackets::ecss::tm::{PusTmCreator, PusTmSecondaryHeader}; use satrs::spacepackets::ecss::tm::{PusTmCreator, PusTmSecondaryHeader};
use satrs::spacepackets::SpHeader; use satrs::spacepackets::SpHeader;
use satrs_example::TimeStampHelper; use satrs_example::{DeviceMode, TimeStampHelper};
use std::sync::mpsc::{self}; use std::sync::mpsc::{self};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
@ -20,6 +20,10 @@ use crate::requests::CompositeRequest;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
const GAUSS_TO_MICROTESLA_FACTOR: f32 = 100.0;
// This is the selected resoltion for the STM LIS3MDL device for the 4 Gauss sensitivity setting.
const FIELD_LSB_PER_GAUSS_4_SENS: f32 = 1.0 / 6842.0;
pub trait SpiInterface { pub trait SpiInterface {
type Error; type Error;
fn transfer(&mut self, tx: &[u8], rx: &mut [u8]) -> Result<(), Self::Error>; fn transfer(&mut self, tx: &[u8], rx: &mut [u8]) -> Result<(), Self::Error>;
@ -27,9 +31,9 @@ pub trait SpiInterface {
#[derive(Default)] #[derive(Default)]
pub struct SpiDummyInterface { pub struct SpiDummyInterface {
dummy_val_0: i16, pub dummy_val_0: i16,
dummy_val_1: i16, pub dummy_val_1: i16,
dummy_val_2: i16, pub dummy_val_2: i16,
} }
impl SpiInterface for SpiDummyInterface { impl SpiInterface for SpiDummyInterface {
@ -45,6 +49,7 @@ impl SpiInterface for SpiDummyInterface {
#[derive(Default, Debug, Copy, Clone, Serialize, Deserialize)] #[derive(Default, Debug, Copy, Clone, Serialize, Deserialize)]
pub struct MgmData { pub struct MgmData {
pub valid: bool,
pub x: f32, pub x: f32,
pub y: f32, pub y: f32,
pub z: f32, pub z: f32,
@ -56,9 +61,10 @@ pub struct MpscModeLeafInterface {
pub reply_tx_to_parent: mpsc::Sender<GenericMessage<ModeReply>>, pub reply_tx_to_parent: mpsc::Sender<GenericMessage<ModeReply>>,
} }
/// Example MGM device handler strongly based on the LIS3MDL MEMS device.
#[derive(new)] #[derive(new)]
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub struct MgmHandler<ComInterface: SpiInterface, TmSender: EcssTmSenderCore> { pub struct MgmHandlerLis3Mdl<ComInterface: SpiInterface, TmSender: EcssTmSenderCore> {
id: UniqueApidTargetId, id: UniqueApidTargetId,
dev_str: &'static str, dev_str: &'static str,
mode_interface: MpscModeLeafInterface, mode_interface: MpscModeLeafInterface,
@ -68,26 +74,41 @@ pub struct MgmHandler<ComInterface: SpiInterface, TmSender: EcssTmSenderCore> {
com_interface: ComInterface, com_interface: ComInterface,
shared_mgm_set: Arc<Mutex<MgmData>>, shared_mgm_set: Arc<Mutex<MgmData>>,
#[new(value = "ModeAndSubmode::new(satrs_example::DeviceMode::Off as u32, 0)")] #[new(value = "ModeAndSubmode::new(satrs_example::DeviceMode::Off as u32, 0)")]
mode: ModeAndSubmode, mode_and_submode: ModeAndSubmode,
#[new(default)] #[new(default)]
tx_buf: [u8; 12], tx_buf: [u8; 12],
#[new(default)] #[new(default)]
rx_buf: [u8; 12], rx_buf: [u8; 12],
#[new(default)] #[new(default)]
tm_buf: [u8; 12], tm_buf: [u8; 16],
#[new(default)]
stamp_helper: TimeStampHelper, stamp_helper: TimeStampHelper,
} }
impl<ComInterface: SpiInterface, TmSender: EcssTmSenderCore> MgmHandler<ComInterface, TmSender> { impl<ComInterface: SpiInterface, TmSender: EcssTmSenderCore>
MgmHandlerLis3Mdl<ComInterface, TmSender>
{
pub fn periodic_operation(&mut self) { pub fn periodic_operation(&mut self) {
self.stamp_helper.update_from_now(); self.stamp_helper.update_from_now();
// Handle requests. // Handle requests.
self.handle_composite_requests(); self.handle_composite_requests();
self.handle_mode_requests(); self.handle_mode_requests();
// Communicate with the device. if self.mode() == DeviceMode::Normal as u32 {
let result = self.com_interface.transfer(&self.tx_buf, &mut self.rx_buf); log::trace!("polling LIS3MDL sensor {}", self.dev_str);
assert!(result.is_ok()); // Communicate with the device.
// TODO: Convert the raw data back to floats. let result = self.com_interface.transfer(&self.tx_buf, &mut self.rx_buf);
assert!(result.is_ok());
// Actual data begins on the second byte, similarly to how a lot of SPI devices behave.
let x_raw = i16::from_be_bytes(self.rx_buf[1..3].try_into().unwrap());
let y_raw = i16::from_be_bytes(self.rx_buf[3..5].try_into().unwrap());
let z_raw = i16::from_be_bytes(self.rx_buf[5..7].try_into().unwrap());
// Simple scaling to retrieve the float value, assuming a sensor resolution of
let mut mgm_guard = self.shared_mgm_set.lock().unwrap();
mgm_guard.x = x_raw as f32 * GAUSS_TO_MICROTESLA_FACTOR * FIELD_LSB_PER_GAUSS_4_SENS;
mgm_guard.y = y_raw as f32 * GAUSS_TO_MICROTESLA_FACTOR * FIELD_LSB_PER_GAUSS_4_SENS;
mgm_guard.z = z_raw as f32 * GAUSS_TO_MICROTESLA_FACTOR * FIELD_LSB_PER_GAUSS_4_SENS;
drop(mgm_guard);
}
} }
pub fn handle_composite_requests(&mut self) { pub fn handle_composite_requests(&mut self) {
@ -134,13 +155,14 @@ impl<ComInterface: SpiInterface, TmSender: EcssTmSenderCore> MgmHandler<ComInter
0, 0,
Some(self.stamp_helper.stamp()), Some(self.stamp_helper.stamp()),
); );
// Let's serialize it as JSON for now.. This is a lot simpler than binary
// serialization.
let mgm_snapshot = *self.shared_mgm_set.lock().unwrap(); let mgm_snapshot = *self.shared_mgm_set.lock().unwrap();
self.tm_buf[0..4].copy_from_slice(&mgm_snapshot.x.to_be_bytes()); // Use binary serialization here. We want the data to be tightly packed.
self.tm_buf[4..8].copy_from_slice(&mgm_snapshot.y.to_be_bytes()); self.tm_buf[0] = mgm_snapshot.valid as u8;
self.tm_buf[8..12].copy_from_slice(&mgm_snapshot.z.to_be_bytes()); self.tm_buf[1..5].copy_from_slice(&mgm_snapshot.x.to_be_bytes());
let hk_tm = PusTmCreator::new(&mut sp_header, sec_header, &self.tm_buf[0..12], true); self.tm_buf[5..9].copy_from_slice(&mgm_snapshot.y.to_be_bytes());
self.tm_buf[9..13].copy_from_slice(&mgm_snapshot.z.to_be_bytes());
let hk_tm =
PusTmCreator::new(&mut sp_header, sec_header, &self.tm_buf[0..12], true);
self.tm_sender self.tm_sender
.send_tm(self.id.id(), PusTmVariant::Direct(hk_tm)) .send_tm(self.id.id(), PusTmVariant::Direct(hk_tm))
.expect("failed to send HK TM"); .expect("failed to send HK TM");
@ -179,15 +201,15 @@ impl<ComInterface: SpiInterface, TmSender: EcssTmSenderCore> MgmHandler<ComInter
} }
impl<ComInterface: SpiInterface, TmSender: EcssTmSenderCore> ModeProvider impl<ComInterface: SpiInterface, TmSender: EcssTmSenderCore> ModeProvider
for MgmHandler<ComInterface, TmSender> for MgmHandlerLis3Mdl<ComInterface, TmSender>
{ {
fn mode_and_submode(&self) -> ModeAndSubmode { fn mode_and_submode(&self) -> ModeAndSubmode {
self.mode self.mode_and_submode
} }
} }
impl<ComInterface: SpiInterface, TmSender: EcssTmSenderCore> ModeRequestHandler impl<ComInterface: SpiInterface, TmSender: EcssTmSenderCore> ModeRequestHandler
for MgmHandler<ComInterface, TmSender> for MgmHandlerLis3Mdl<ComInterface, TmSender>
{ {
type Error = ModeError; type Error = ModeError;
fn start_transition( fn start_transition(
@ -195,14 +217,22 @@ impl<ComInterface: SpiInterface, TmSender: EcssTmSenderCore> ModeRequestHandler
requestor: MessageMetadata, requestor: MessageMetadata,
mode_and_submode: ModeAndSubmode, mode_and_submode: ModeAndSubmode,
) -> Result<(), satrs::mode::ModeError> { ) -> Result<(), satrs::mode::ModeError> {
self.mode = mode_and_submode; log::info!(
"{}: transitioning to mode {:?}",
self.dev_str,
mode_and_submode
);
self.mode_and_submode = mode_and_submode;
self.handle_mode_reached(Some(requestor))?; self.handle_mode_reached(Some(requestor))?;
Ok(()) Ok(())
} }
fn announce_mode(&self, _requestor_info: Option<MessageMetadata>, _recursive: bool) { fn announce_mode(&self, _requestor_info: Option<MessageMetadata>, _recursive: bool) {
log::info!("{} announcing mode: {:?}", self.dev_str, self.mode); log::info!(
// TODO: Trigger event. "{} announcing mode: {:?}",
self.dev_str,
self.mode_and_submode
);
} }
fn handle_mode_reached( fn handle_mode_reached(

View File

@ -27,7 +27,7 @@ use satrs_example::config::{OBSW_SERVER_ADDR, SERVER_PORT};
use tmtc::PusTcSourceProviderDynamic; use tmtc::PusTcSourceProviderDynamic;
use udp::DynamicUdpTmHandler; use udp::DynamicUdpTmHandler;
use crate::acs::mgm::{MgmHandler, MpscModeLeafInterface, SpiDummyInterface}; use crate::acs::mgm::{MgmHandlerLis3Mdl, MpscModeLeafInterface, SpiDummyInterface};
use crate::ccsds::CcsdsReceiver; use crate::ccsds::CcsdsReceiver;
use crate::logger::setup_logger; use crate::logger::setup_logger;
use crate::pus::action::{create_action_service_dynamic, create_action_service_static}; use crate::pus::action::{create_action_service_dynamic, create_action_service_static};
@ -211,7 +211,7 @@ fn static_tmtc_pool_main() {
reply_tx_to_pus: pus_mode_reply_tx, reply_tx_to_pus: pus_mode_reply_tx,
reply_tx_to_parent: mgm_handler_mode_reply_to_parent_tx, reply_tx_to_parent: mgm_handler_mode_reply_to_parent_tx,
}; };
let mut mgm_handler = MgmHandler::new( let mut mgm_handler = MgmHandlerLis3Mdl::new(
MGM_HANDLER_0, MGM_HANDLER_0,
"MGM_0", "MGM_0",
mode_leaf_interface, mode_leaf_interface,
@ -429,7 +429,7 @@ fn dyn_tmtc_pool_main() {
reply_tx_to_pus: pus_mode_reply_tx, reply_tx_to_pus: pus_mode_reply_tx,
reply_tx_to_parent: mgm_handler_mode_reply_to_parent_tx, reply_tx_to_parent: mgm_handler_mode_reply_to_parent_tx,
}; };
let mut mgm_handler = MgmHandler::new( let mut mgm_handler = MgmHandlerLis3Mdl::new(
MGM_HANDLER_0, MGM_HANDLER_0,
"MGM_0", "MGM_0",
mode_leaf_interface, mode_leaf_interface,

View File

@ -179,6 +179,14 @@ impl From<GenericTargetedMessagingError> for ModeError {
pub trait ModeProvider { pub trait ModeProvider {
fn mode_and_submode(&self) -> ModeAndSubmode; fn mode_and_submode(&self) -> ModeAndSubmode;
fn mode(&self) -> Mode {
self.mode_and_submode().mode()
}
fn submode(&self) -> Submode {
self.mode_and_submode().submode()
}
} }
pub trait ModeRequestHandler: ModeProvider { pub trait ModeRequestHandler: ModeProvider {