first step in unifying the examples: one example folder

One example folder for all examples, including std and embedded examples
This commit is contained in:
Robin Mueller committed 2026-09-29 13:39:39 +02:00
1 parent bb8decfc74
commit 7cc2ff3ff9
130 files changed
+399 -368

No files matched your search

+16
View File
@@ -0,0 +1,16 @@
[package]
name = "types"
version = "0.1.0"
edition = "2024"
[dependencies]
serde = { version = "1", default-features = false, features = ["derive"] }
spacepackets = { version = "0.18", default-features = false, features = ["serde"] }
satrs = { path = "../../satrs", default-features = false, features = ["serde"] }
num_enum = { version = "0.7", default-features = false }
strum = { version = "0.28", default-features = false, features = ["derive"] }
postcard = { version = "1", features = ["alloc"] }
thiserror = { version = "2", default-features = false }
bitbybit = "2"
arbitrary-int = "2"
heapless = { version = "0.9", features = ["serde"] }
+39
View File
@@ -0,0 +1,39 @@
#[derive(
serde::Serialize,
serde::Deserialize,
Debug,
Clone,
Copy,
PartialEq,
Eq,
num_enum::IntoPrimitive,
num_enum::TryFromPrimitive,
)]
#[repr(u32)]
pub enum Mode {
Passive,
Safe,
Idle,
}
pub mod request {
use super::Mode;
#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum ModeRequest {
SetMode(Mode),
ReadMode,
}
}
pub mod response {
use super::Mode;
#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum ModeReport {
/// Mode of the assembly.
Mode(super::Mode),
/// Children are in wrong mode after commanding.
WrongMode([Option<Mode>; 2]),
}
}
+118
View File
@@ -0,0 +1,118 @@
pub mod request {
use crate::{DeviceMode, HealthRequest, HkRequestType, Message};
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
pub enum ModeRequest {
SetMode(DeviceMode),
ReadMode,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, serde::Serialize, serde::Deserialize)]
pub enum HkId {
Sensor,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, serde::Serialize, serde::Deserialize)]
pub struct HkRequest {
pub id: HkId,
pub req_type: HkRequestType,
}
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug)]
pub enum Request {
Ping,
Hk(HkRequest),
Mode(ModeRequest),
Health(HealthRequest),
}
impl Request {
fn message_type(&self) -> crate::MessageType {
match self {
Request::Ping => crate::MessageType::Verification,
Request::Hk(_hk_request) => crate::MessageType::Hk,
Request::Mode(_mode) => crate::MessageType::Mode,
Request::Health(_health) => crate::MessageType::Health,
}
}
}
impl Message for Request {
fn message_type(&self) -> crate::MessageType {
self.message_type()
}
}
}
#[derive(Default, Debug, Copy, Clone, serde::Serialize, serde::Deserialize)]
pub struct SensorData {
pub valid: bool,
pub x: f32,
pub y: f32,
pub z: f32,
}
#[derive(strum::EnumDiscriminants, serde::Serialize, serde::Deserialize, Clone, Copy, Debug)]
#[strum_discriminants(derive(num_enum::IntoPrimitive))]
#[repr(u16)]
pub enum Event {
/// The SPI fault counter exceeded its threshold. Followed by a recovery event.
SpiFaultThresholdExceeded,
/// A commanded or autonomous mode transition completed.
ModeChanged(crate::DeviceMode),
Recovery(satrs::fdir::RecoveryEvent),
}
impl crate::Message for Event {
fn message_type(&self) -> crate::MessageType {
crate::MessageType::Event
}
}
impl crate::EventId for Event {
fn event_id(&self) -> u16 {
match self {
Event::Recovery(event) => crate::recovery_event_id(*event),
_ => EventDiscriminants::from(self).into(),
}
}
}
pub mod response {
use crate::{DeviceMode, Message, acs::mgm::SensorData};
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug)]
pub enum HkResponse {
MgmData(SensorData),
}
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Copy)]
pub enum ModeResponse {
/// New mode has been set.
Mode(DeviceMode),
/// Setting a mode timed out.
SetModeTimeout,
}
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug)]
pub enum Response {
Ok,
Hk(HkResponse),
Mode(ModeResponse),
}
impl Response {
fn message_type(&self) -> crate::MessageType {
match self {
Response::Ok => crate::MessageType::Verification,
Response::Hk(_hk_response) => crate::MessageType::Hk,
Response::Mode(_mode_failure) => crate::MessageType::Mode,
}
}
}
impl Message for Response {
fn message_type(&self) -> crate::MessageType {
self.message_type()
}
}
}
+158
View File
@@ -0,0 +1,158 @@
use core::str::FromStr;
use num_enum::TryFromPrimitive as _;
use satrs::mode::ModeRaw;
use crate::DeviceMode;
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
pub enum Mode {
/// The assembly mode ressembles the modes of the devices it controls. It also tries to keep
/// the children in the correct mode by re-commanding them into the correct mode.
Device(DeviceMode),
/// Mode keeping disabled.
NoModeKeeping,
}
impl From<Mode> for ModeRaw {
fn from(value: Mode) -> Self {
match value {
Mode::Device(device_mode) => device_mode.into(),
Mode::NoModeKeeping => 5,
}
}
}
impl TryFrom<ModeRaw> for Mode {
type Error = ();
fn try_from(value: ModeRaw) -> Result<Self, Self::Error> {
match DeviceMode::try_from_primitive(value) {
Ok(val) => Ok(Mode::Device(val)),
Err(_) => {
if value == 5 {
Ok(Mode::NoModeKeeping)
} else {
Err(())
}
}
}
}
}
impl FromStr for Mode {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"off" => Ok(Mode::Device(DeviceMode::Off)),
"on" => Ok(Mode::Device(DeviceMode::On)),
"normal" => Ok(Mode::Device(DeviceMode::Normal)),
"no_mode_keeping" => Ok(Mode::NoModeKeeping),
_ => Err(()),
}
}
}
pub mod request {
use crate::{HkRequestType, Message, acs::mgm_assembly::Mode};
#[derive(Debug, PartialEq, Eq, Clone, Copy, serde::Serialize, serde::Deserialize)]
pub enum HkId {
Sensor,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum ModeRequest {
SetMode(Mode),
ReadMode,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, serde::Serialize, serde::Deserialize)]
pub struct HkRequest {
pub id: HkId,
pub req_type: HkRequestType,
}
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug)]
pub enum Request {
Ping,
Mode(ModeRequest),
}
impl Request {
fn message_type(&self) -> crate::MessageType {
match self {
Request::Ping => crate::MessageType::Verification,
Request::Mode(_mode) => crate::MessageType::Mode,
}
}
}
impl Message for Request {
fn message_type(&self) -> crate::MessageType {
self.message_type()
}
}
}
pub mod response {
use crate::{DeviceMode, Message};
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug, PartialEq, Eq)]
pub enum ModeCommandFailure {
Timeout,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum ModeResponse {
/// Mode of the assembly.
Mode(super::Mode),
/// Timeout failure setting the children modes.
SetModeTimeout([Option<DeviceMode>; 2]),
/// Children are in wrong mode after commanding.
WrongMode([Option<DeviceMode>; 2]),
/// An assembly tried modekeeping but can not keep its mode.
CanNotKeepMode([Option<DeviceMode>; 2]),
}
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug, PartialEq, Eq)]
pub enum Response {
Ok,
Mode(ModeResponse),
}
impl Response {
fn message_type(&self) -> crate::MessageType {
match self {
Response::Ok => crate::MessageType::Verification,
Response::Mode(_mode_report) => crate::MessageType::Mode,
}
}
}
impl Message for Response {
fn message_type(&self) -> crate::MessageType {
self.message_type()
}
}
}
#[derive(strum::EnumDiscriminants, serde::Serialize, serde::Deserialize, Clone, Copy, Debug)]
#[strum_discriminants(derive(num_enum::IntoPrimitive))]
#[repr(u16)]
pub enum Event {
/// A commanded or autonomous mode transition completed.
ModeChanged(Mode),
}
impl crate::Message for Event {
fn message_type(&self) -> crate::MessageType {
crate::MessageType::Event
}
}
impl crate::EventId for Event {
fn event_id(&self) -> u16 {
EventDiscriminants::from(self).into()
}
}
+136
View File
@@ -0,0 +1,136 @@
use crate::DeviceMode;
/// Commanded magnetic dipole. Simple model using raw values per axis.
#[derive(serde::Serialize, serde::Deserialize, Default, Debug, Clone, Copy, PartialEq, Eq)]
pub struct Dipole {
pub x: i16,
pub y: i16,
pub z: i16,
}
impl Dipole {
pub const LEN: usize = 6;
pub fn to_be_bytes(&self) -> [u8; Self::LEN] {
let [x0, x1] = self.x.to_be_bytes();
let [y0, y1] = self.y.to_be_bytes();
let [z0, z1] = self.z.to_be_bytes();
[x0, x1, y0, y1, z0, z1]
}
pub fn from_be_bytes(bytes: &[u8; Self::LEN]) -> Self {
let [x0, x1, y0, y1, z0, z1] = *bytes;
Self {
x: i16::from_be_bytes([x0, x1]),
y: i16::from_be_bytes([y0, y1]),
z: i16::from_be_bytes([z0, z1]),
}
}
}
#[derive(serde::Serialize, serde::Deserialize, Default, Debug, Clone, Copy, PartialEq, Eq)]
pub struct HkSet {
pub valid: bool,
pub dipole: Dipole,
pub torquing: bool,
}
pub mod request {
use crate::{DeviceMode, HealthRequest, HkRequestType, Message};
use super::Dipole;
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
pub enum ModeRequest {
SetMode(DeviceMode),
ReadMode,
}
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug)]
pub enum Request {
Ping,
Hk(HkRequestType),
Mode(ModeRequest),
/// Only accepted in normal mode.
ApplyTorque {
dipole: Dipole,
duration: core::time::Duration,
},
Health(HealthRequest),
}
impl Message for Request {
fn message_type(&self) -> crate::MessageType {
match self {
Request::Ping => crate::MessageType::Verification,
Request::Hk(_) => crate::MessageType::Hk,
Request::Mode(_) => crate::MessageType::Mode,
Request::ApplyTorque { .. } => crate::MessageType::Action,
Request::Health(_) => crate::MessageType::Health,
}
}
}
}
#[derive(strum::EnumDiscriminants, serde::Serialize, serde::Deserialize, Clone, Copy, Debug)]
#[strum_discriminants(derive(num_enum::IntoPrimitive))]
#[repr(u16)]
pub enum Event {
/// A commanded mode transition completed.
ModeChanged(DeviceMode),
/// Too many requests were not answered correctly. Followed by a recovery event.
ReplyFaultThresholdExceeded,
Recovery(satrs::fdir::RecoveryEvent),
}
impl crate::Message for Event {
fn message_type(&self) -> crate::MessageType {
crate::MessageType::Event
}
}
impl crate::EventId for Event {
fn event_id(&self) -> u16 {
match self {
Event::Recovery(event) => crate::recovery_event_id(*event),
_ => EventDiscriminants::from(self).into(),
}
}
}
pub mod response {
use crate::{DeviceMode, Message};
use super::HkSet;
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
pub enum ModeResponse {
/// New mode has been set.
Mode(DeviceMode),
/// Setting a mode timed out.
SetModeTimeout,
}
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug, PartialEq, Eq)]
pub enum Response {
Ok,
Hk(HkSet),
Mode(ModeResponse),
/// The command requires the device to be in normal mode.
NotInNormalMode,
/// The device did not answer the command with a valid reply in time.
ReplyTimeout,
}
impl Message for Response {
fn message_type(&self) -> crate::MessageType {
match self {
Response::Ok | Response::NotInNormalMode | Response::ReplyTimeout => {
crate::MessageType::Verification
}
Response::Hk(_) => crate::MessageType::Hk,
Response::Mode(_) => crate::MessageType::Mode,
}
}
}
}
+5
View File
@@ -0,0 +1,5 @@
pub mod ctrl;
pub mod mgm;
pub mod mgm_assembly;
pub mod mgt;
pub mod subsystem;
+99
View File
@@ -0,0 +1,99 @@
#[derive(
serde::Serialize,
serde::Deserialize,
Debug,
Clone,
Copy,
PartialEq,
Eq,
num_enum::TryFromPrimitive,
num_enum::IntoPrimitive,
)]
#[repr(u32)]
pub enum Mode {
Off = 0,
Safe = 1,
}
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
pub enum ModeChild {
MgmAssembly,
Mgt,
Controller,
}
impl Mode {
/// Each subsystem mode has a fallback mode to allow clean transitions.
pub const fn fallback_mode(&self) -> Self {
match self {
Mode::Off => Mode::Safe,
Mode::Safe => Mode::Safe,
}
}
}
pub mod request {
use crate::Message;
#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum ModeRequest {
SetMode(super::Mode),
ReadMode,
}
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug)]
pub enum Request {
Ping,
Mode(ModeRequest),
}
impl Request {
fn message_type(&self) -> crate::MessageType {
match self {
Request::Ping => crate::MessageType::Verification,
Request::Mode(_mode) => crate::MessageType::Mode,
}
}
}
impl Message for Request {
fn message_type(&self) -> crate::MessageType {
self.message_type()
}
}
}
pub mod response {
use crate::Message;
#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum ModeResponse {
/// Mode of the subsystem.
Mode(super::Mode),
/// Command timeout when commanding a child.
CommandTimeout(super::ModeChild),
/// Can not keep mode because a child changed mode unexpectedly.
CanNotKeepMode(super::ModeChild),
}
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug)]
pub enum Response {
Ok,
Mode(ModeResponse),
}
impl Response {
fn message_type(&self) -> crate::MessageType {
match self {
Response::Ok => crate::MessageType::Verification,
Response::Mode(_mode_report) => crate::MessageType::Mode,
}
}
}
impl Message for Response {
fn message_type(&self) -> crate::MessageType {
self.message_type()
}
}
}
+130
View File
@@ -0,0 +1,130 @@
use crate::TmHeader;
use serde::Serialize;
use spacepackets::{
CcsdsPacketCreationError, CcsdsPacketCreatorWithReservedData, SpHeader, SpacePacketHeader,
ccsds_packet_len_for_user_data_len_with_checksum,
};
use crate::TcHeader;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CcsdsTcPacketOwned {
pub sp_header: SpacePacketHeader,
pub tc_header: TcHeader,
pub payload: alloc::vec::Vec<u8>,
}
impl CcsdsTcPacketOwned {
pub fn new_with_request<R: serde::Serialize>(
sp_header: SpacePacketHeader,
tc_header: TcHeader,
request: R,
) -> Self {
let request_serialized = postcard::to_allocvec(&request).unwrap();
Self::new(sp_header, tc_header, request_serialized)
}
pub fn new(
sp_header: SpacePacketHeader,
tc_header: TcHeader,
payload: alloc::vec::Vec<u8>,
) -> Self {
Self {
sp_header,
tc_header,
payload,
}
}
pub fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, CcsdsCreationError> {
let response_len =
postcard::experimental::serialized_size(&self.tc_header)? + self.payload.len();
let mut ccsds_tc = CcsdsPacketCreatorWithReservedData::new_tc_with_checksum(
self.sp_header,
response_len,
buf,
)?;
let user_data = ccsds_tc.packet_data_mut();
let ser_len = postcard::to_slice(&self.tc_header, user_data)?.len();
user_data[ser_len..ser_len + self.payload.len()].copy_from_slice(&self.payload);
let ccsds_packet_len = ccsds_tc.finish();
Ok(ccsds_packet_len)
}
pub fn len_written(&self) -> usize {
ccsds_packet_len_for_user_data_len_with_checksum(
postcard::experimental::serialized_size(&self.tc_header).unwrap() as usize
+ self.payload.len(),
)
.unwrap()
}
pub fn to_vec(&self) -> alloc::vec::Vec<u8> {
let mut buf = alloc::vec![0u8; self.len_written()];
let len = self.write_to_bytes(&mut buf).unwrap();
buf.truncate(len);
buf
}
}
#[derive(Debug, thiserror::Error)]
pub enum CcsdsCreationError {
#[error("CCSDS packet creation error: {0}")]
CcsdsPacketCreation(#[from] CcsdsPacketCreationError),
#[error("postcard error: {0}")]
Postcard(#[from] postcard::Error),
#[error("timestamp generation error")]
Time,
}
/// Unserialized owned TM packet which can be cloned and sent around.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CcsdsTmPacketOwned {
pub sp_header: SpacePacketHeader,
pub tm_header: TmHeader,
pub payload: alloc::vec::Vec<u8>,
}
impl CcsdsTmPacketOwned {
pub fn new_with_serde_payload(
sp_header: SpHeader,
tm_header: &TmHeader,
payload: &impl Serialize,
) -> Result<Self, postcard::Error> {
Ok(CcsdsTmPacketOwned {
sp_header,
tm_header: *tm_header,
payload: postcard::to_allocvec(&payload)?,
})
}
pub fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, CcsdsCreationError> {
let response_len =
postcard::experimental::serialized_size(&self.tm_header)? + self.payload.len();
let mut ccsds_tm = CcsdsPacketCreatorWithReservedData::new_tm_with_checksum(
self.sp_header,
response_len,
buf,
)?;
let user_data = ccsds_tm.packet_data_mut();
let ser_len = postcard::to_slice(&self.tm_header, user_data)?.len();
user_data[ser_len..ser_len + self.payload.len()].copy_from_slice(&self.payload);
let ccsds_packet_len = ccsds_tm.finish();
Ok(ccsds_packet_len)
}
pub fn len_written(&self) -> usize {
ccsds_packet_len_for_user_data_len_with_checksum(
postcard::experimental::serialized_size(&self.tm_header).unwrap() as usize
+ self.payload.len(),
)
.unwrap()
}
pub fn to_vec(&self) -> alloc::vec::Vec<u8> {
let mut buf = alloc::vec![0u8; self.len_written()];
let len = self.write_to_bytes(&mut buf).unwrap();
buf.truncate(len);
buf
}
}
+57
View File
@@ -0,0 +1,57 @@
use crate::{EventId, Message};
#[derive(strum::EnumDiscriminants, serde::Serialize, serde::Deserialize, Clone, Copy, Debug)]
#[strum_discriminants(derive(num_enum::IntoPrimitive))]
#[repr(u16)]
pub enum Event {
TestEvent,
}
impl Message for Event {
fn message_type(&self) -> crate::MessageType {
crate::MessageType::Event
}
}
impl EventId for Event {
fn event_id(&self) -> u16 {
EventDiscriminants::from(self).into()
}
}
pub mod request {
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug)]
pub enum Request {
Ping,
TestEvent,
}
}
pub mod response {
use crate::Message;
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug)]
pub enum Response {
Ok,
Event(super::Event),
}
impl Message for Response {
fn message_type(&self) -> crate::MessageType {
match self {
Response::Ok => crate::MessageType::Verification,
Response::Event(_event) => crate::MessageType::Event,
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_event_id() {
assert_eq!(Event::TestEvent.event_id(), 0);
}
}
+34
View File
@@ -0,0 +1,34 @@
pub mod request {
use crate::ComponentId;
/// Controls which events are converted to TM. Component and event filters are independent:
/// re-enabling a component does not re-enable its individually disabled events.
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug, PartialEq, Eq)]
pub enum Request {
EnableComponent(ComponentId),
DisableComponent(ComponentId),
EnableEvent {
sender_id: ComponentId,
event_id: u16,
},
DisableEvent {
sender_id: ComponentId,
event_id: u16,
},
}
}
pub mod response {
use crate::Message;
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug, PartialEq, Eq)]
pub enum Response {
Ok,
}
impl Message for Response {
fn message_type(&self) -> crate::MessageType {
crate::MessageType::Verification
}
}
}
+243
View File
@@ -0,0 +1,243 @@
#![no_std]
extern crate alloc;
use core::str::FromStr;
use spacepackets::{
CcsdsPacketIdAndPsc,
time::cds::{CdsTime, MIN_CDS_FIELD_LEN},
};
pub mod acs;
pub mod ccsds;
pub mod control;
pub mod event_manager;
pub mod pcdu;
pub mod tmtc;
#[derive(
Debug,
Copy,
Clone,
PartialEq,
Eq,
Hash,
serde::Serialize,
serde::Deserialize,
num_enum::TryFromPrimitive,
num_enum::IntoPrimitive,
)]
#[repr(u32)]
pub enum ComponentId {
Controller,
AcsSubsystem,
AcsMgmAssembly,
AcsController,
AcsMgm0,
AcsMgm1,
AcsMgt,
EpsSubsystem,
EpsPcdu,
UdpServer,
TcpServer,
EventManager,
Ground,
}
#[derive(Debug, PartialEq, Eq, strum::EnumIter)]
#[bitbybit::bitenum(u11)]
pub enum Apid {
Tmtc = 1,
Cfdp = 2,
Acs = 3,
Eps = 6,
}
#[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize)]
pub enum Event {
ControllerEvent(control::Event),
}
impl Message for Event {
fn message_type(&self) -> MessageType {
MessageType::Event
}
}
impl EventId for Event {
fn event_id(&self) -> u16 {
match self {
Event::ControllerEvent(event) => event.event_id(),
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[non_exhaustive]
pub struct TmHeader {
pub sender_id: ComponentId,
pub target_id: ComponentId,
pub message_type: MessageType,
/// Telemetry can either be sent unsolicited, or as a response to telecommands.
pub tc_id: Option<CcsdsPacketIdAndPsc>,
/// Raw CDS short timestamp.
pub timestamp: Option<[u8; 7]>,
}
impl TmHeader {
pub fn new(
sender_id: ComponentId,
target_id: ComponentId,
message_type: MessageType,
tc_id: Option<CcsdsPacketIdAndPsc>,
cds_timestamp: &CdsTime,
) -> Self {
// Can not fail, CDS short always requires 7 bytes.
let mut stamp_buf: [u8; MIN_CDS_FIELD_LEN] = [0; MIN_CDS_FIELD_LEN];
cds_timestamp.write_to_bytes(&mut stamp_buf).unwrap();
Self {
sender_id,
target_id,
tc_id,
message_type,
timestamp: Some(stamp_buf),
}
}
pub fn new_for_unsolicited_tm(
sender_id: ComponentId,
target_id: ComponentId,
message_type: MessageType,
cds_timestamp: &CdsTime,
) -> Self {
Self::new(sender_id, target_id, message_type, None, cds_timestamp)
}
pub fn new_for_tc_response(
sender_id: ComponentId,
target_id: ComponentId,
message_type: MessageType,
tc_id: CcsdsPacketIdAndPsc,
cds_timestamp: &CdsTime,
) -> Self {
Self::new(
sender_id,
target_id,
message_type,
Some(tc_id),
cds_timestamp,
)
}
pub fn from_bytes_postcard(data: &[u8]) -> Result<(Self, &[u8]), postcard::Error> {
postcard::take_from_bytes::<TmHeader>(data)
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[non_exhaustive]
pub struct TcHeader {
pub target_id: ComponentId,
pub request_type: MessageType,
}
impl TcHeader {
pub fn new(target_id: ComponentId, request_type: MessageType) -> Self {
Self {
target_id,
request_type,
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum MessageType {
Ping,
Mode,
Hk,
Action,
Event,
Verification,
Health,
}
pub trait Message {
fn message_type(&self) -> MessageType;
}
/// Stable, per-variant numeric identifier for an event, meant to be referenced from ground
/// (e.g. to enable/disable TM generation for one specific event) and to stay the same across
/// releases. Unlike [core::mem::discriminant], this can be constructed from a raw number
/// received in a telecommand.
pub trait EventId {
fn event_id(&self) -> u16;
}
/// Start of the event ID range for generic FDIR events, which are embedded into the event types
/// of the components. These events have the same ID for all components.
pub const FDIR_EVENT_ID_BASE: u16 = 0x100;
pub const fn recovery_event_id(event: satrs::fdir::RecoveryEvent) -> u16 {
FDIR_EVENT_ID_BASE + event as u16
}
/// Generic device mode which covers the requirements of most devices.
///
/// The states are related both to the physical and the logical state of the device. Some
/// device handlers control the power supply of their own device and an off state might also
/// mean that the device is physically off.
#[derive(
serde::Serialize,
serde::Deserialize,
Debug,
PartialEq,
Eq,
Copy,
Clone,
num_enum::IntoPrimitive,
num_enum::TryFromPrimitive,
)]
#[repr(u32)]
pub enum DeviceMode {
Off = 0,
On = 1,
/// Normal operation mode where periodic polling might be done as well.
Normal = 2,
}
impl FromStr for DeviceMode {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"off" => Ok(DeviceMode::Off),
"on" => Ok(DeviceMode::On),
"normal" => Ok(DeviceMode::Normal),
_ => Err(()),
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum HealthRequest {
/// Overrides the device's autonomous FDIR health state, for example to clear a `Faulty`
/// state set by the handler after ground has fixed or worked around the underlying issue.
SetHealth(satrs::health::HealthState),
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[non_exhaustive]
pub enum HkRequestType {
OneShot,
/// Enable periodic HK generation. Without an interval, the current interval is kept.
EnablePeriodic(Option<core::time::Duration>),
DisablePeriodic,
/// Modify periodic HK generation interval.
ModifyInterval(core::time::Duration),
}
#[cfg(test)]
mod tests {}
+168
View File
@@ -0,0 +1,168 @@
use strum::{EnumCount as _, IntoEnumIterator as _};
#[bitbybit::bitfield(u16, debug, default = 0x0)]
#[derive(serde::Serialize, serde::Deserialize)]
pub struct SwitchesBitfield {
#[bit(2, rw)]
magnetorquer: bool,
#[bit(1, rw)]
mgm1: bool,
#[bit(0, rw)]
mgm0: bool,
}
#[derive(
Debug,
Copy,
Clone,
PartialEq,
Eq,
serde::Serialize,
serde::Deserialize,
Hash,
strum::EnumIter,
strum::EnumCount,
num_enum::IntoPrimitive,
num_enum::TryFromPrimitive,
)]
#[repr(u16)]
pub enum SwitchId {
Mgm0 = 0,
Mgm1 = 1,
Mgt = 2,
}
#[derive(Debug, Eq, PartialEq, Copy, Clone, serde::Serialize, serde::Deserialize)]
pub enum SwitchState {
Off = 0,
On = 1,
Unknown = 2,
Faulty = 3,
}
impl From<SwitchStateBinary> for SwitchState {
fn from(value: SwitchStateBinary) -> Self {
match value {
SwitchStateBinary::Off => SwitchState::Off,
SwitchStateBinary::On => SwitchState::On,
}
}
}
#[derive(Debug, Eq, PartialEq, Copy, Clone, serde::Serialize, serde::Deserialize)]
pub enum SwitchStateBinary {
Off = 0,
On = 1,
}
#[derive(strum::EnumDiscriminants, serde::Serialize, serde::Deserialize, Clone, Copy, Debug)]
#[strum_discriminants(derive(num_enum::IntoPrimitive))]
#[repr(u16)]
pub enum Event {
/// Sending a request to the PCDU over the serial/simulator link failed.
SerialCommError,
}
impl crate::Message for Event {
fn message_type(&self) -> crate::MessageType {
crate::MessageType::Event
}
}
impl crate::EventId for Event {
fn event_id(&self) -> u16 {
EventDiscriminants::from(self).into()
}
}
pub type SwitchMapBinary = heapless::LinearMap<SwitchId, SwitchStateBinary, { SwitchId::COUNT }>;
pub struct SwitchMapBinaryWrapper(pub SwitchMapBinary);
impl Default for SwitchMapBinaryWrapper {
fn default() -> Self {
let mut switch_map = SwitchMapBinary::default();
for entry in SwitchId::iter() {
switch_map
.insert(entry, SwitchStateBinary::Off)
.expect("switch map capacity exceeded");
}
Self(switch_map)
}
}
pub struct SwitchRequest {
pub switch_id: SwitchId,
pub target_state: SwitchStateBinary,
}
impl SwitchRequest {
pub fn new(switch_id: SwitchId, target_state: SwitchStateBinary) -> Self {
Self {
switch_id,
target_state,
}
}
pub fn switch_id(&self) -> SwitchId {
self.switch_id
}
pub fn target_state(&self) -> SwitchStateBinary {
self.target_state
}
}
pub mod request {
use crate::{DeviceMode, Message};
use super::*;
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug)]
pub enum Request {
Mode(DeviceMode),
Ping,
GetSwitches,
EnableSwitches(SwitchesBitfield),
DisableSwitches(SwitchesBitfield),
}
impl Request {
pub fn message_type(&self) -> crate::MessageType {
match self {
Request::Mode(_mode) => crate::MessageType::Mode,
Request::Ping => crate::MessageType::Verification,
Request::GetSwitches => crate::MessageType::Action,
Request::EnableSwitches(_switches) | Request::DisableSwitches(_switches) => {
crate::MessageType::Action
}
}
}
}
impl Message for Request {
fn message_type(&self) -> crate::MessageType {
self.message_type()
}
}
}
pub mod response {
use super::*;
use crate::Message;
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug)]
pub enum Response {
Ok,
Switches(SwitchesBitfield),
}
impl Message for Response {
fn message_type(&self) -> crate::MessageType {
match self {
Response::Ok => crate::MessageType::Verification,
Response::Switches(_switches) => crate::MessageType::Action,
}
}
}
}
+25
View File
@@ -0,0 +1,25 @@
use crate::{ComponentId, EventId, Message};
#[derive(strum::EnumDiscriminants, serde::Serialize, serde::Deserialize, Clone, Copy, Debug)]
#[strum_discriminants(derive(num_enum::IntoPrimitive))]
#[repr(u16)]
pub enum Event {
/// A received CCSDS packet failed CRC or basic format validation.
InvalidTcPacket,
/// The CCSDS packet was valid, but its embedded TC header could not be decoded.
InvalidTcHeader,
/// The TC header decoded fine, but no handler is registered for its target ID.
UnknownTargetId(ComponentId),
}
impl Message for Event {
fn message_type(&self) -> crate::MessageType {
crate::MessageType::Event
}
}
impl EventId for Event {
fn event_id(&self) -> u16 {
EventDiscriminants::from(self).into()
}
}
+49
View File
@@ -0,0 +1,49 @@
use arbitrary_int::u11;
use spacepackets::{SpacePacketHeader, time::cds::CdsTime};
use types::{
Apid, ComponentId, MessageType, TcHeader, TmHeader,
ccsds::{CcsdsTcPacketOwned, CcsdsTmPacketOwned},
};
#[test]
fn tc_len_written_matches_actual_serialized_length() {
let packet = CcsdsTcPacketOwned::new_with_request(
SpacePacketHeader::new_from_apid(u11::new(Apid::Tmtc as u16)),
TcHeader::new(ComponentId::Controller, MessageType::Ping),
types::control::request::Request::Ping,
);
let actual = packet.to_vec();
println!("TC len_written(): {}", packet.len_written());
println!("TC actual len: {}", actual.len());
assert_eq!(packet.len_written(), actual.len());
}
#[test]
fn tm_len_written_matches_actual_serialized_length() {
let timestamp = CdsTime::new_with_u16_days(0, 0);
let tm_header = TmHeader::new(
ComponentId::Controller,
ComponentId::Ground,
MessageType::Verification,
None,
&timestamp,
);
let packet = CcsdsTmPacketOwned::new_with_serde_payload(
SpacePacketHeader::new_from_apid(u11::new(Apid::Tmtc as u16)),
&tm_header,
&types::control::response::Response::Ok,
)
.unwrap();
let actual = packet.to_vec();
println!("TM len_written(): {}", packet.len_written());
println!("TM actual len: {}", actual.len());
assert_eq!(packet.len_written(), actual.len());
}