Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1fafe149c1 | |||
| bcc1cc850f | |||
| 2cab8d7fed | |||
| b6612ed861 | |||
| 9d15dbcf09 |
@@ -9,6 +9,7 @@ members = [
|
|||||||
"satrs-example/minisim",
|
"satrs-example/minisim",
|
||||||
"satrs-shared",
|
"satrs-shared",
|
||||||
"embedded-examples/embedded-client",
|
"embedded-examples/embedded-client",
|
||||||
|
"embedded-examples/models",
|
||||||
]
|
]
|
||||||
|
|
||||||
exclude = [
|
exclude = [
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ toml = "0.9"
|
|||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
satrs-stm32f3-disco-rtic = { path = "../stm32f3-disco-rtic" }
|
satrs-stm32f3-disco-rtic = { path = "../stm32f3-disco-rtic" }
|
||||||
spacepackets = { version = "0.17" }
|
spacepackets = { version = "0.17" }
|
||||||
|
embedded-models = { path = "../models" }
|
||||||
tmtc-utils = { git = "https://egit.irs.uni-stuttgart.de/rust/tmtc-utils.git", version = "0.1" }
|
tmtc-utils = { git = "https://egit.irs.uni-stuttgart.de/rust/tmtc-utils.git", version = "0.1" }
|
||||||
postcard = { version = "1", features = ["alloc"] }
|
postcard = { version = "1", features = ["alloc"] }
|
||||||
cobs = "0.5"
|
cobs = "0.5"
|
||||||
|
|||||||
+8
-49
@@ -1,13 +1,9 @@
|
|||||||
use std::{
|
use std::time::Duration;
|
||||||
fs::File,
|
|
||||||
io::Read,
|
|
||||||
path::Path,
|
|
||||||
time::{Duration, SystemTime},
|
|
||||||
};
|
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use cobs::CobsDecoderOwned;
|
use cobs::CobsDecoderOwned;
|
||||||
use satrs_stm32f3_disco_rtic::Request;
|
use embedded_client::setup_logger;
|
||||||
|
use embedded_models::stm32f3;
|
||||||
use spacepackets::{CcsdsPacketCreatorOwned, CcsdsPacketReader, SpHeader};
|
use spacepackets::{CcsdsPacketCreatorOwned, CcsdsPacketReader, SpHeader};
|
||||||
use tmtc_utils::transport::serial::PacketTransportSerialCobs;
|
use tmtc_utils::transport::serial::PacketTransportSerialCobs;
|
||||||
|
|
||||||
@@ -21,47 +17,11 @@ struct Cli {
|
|||||||
set_led_frequency: Option<u32>,
|
set_led_frequency: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize)]
|
|
||||||
struct Config {
|
|
||||||
interface: Interface,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize)]
|
|
||||||
struct Interface {
|
|
||||||
serial_port: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn setup_logger() -> Result<(), fern::InitError> {
|
|
||||||
fern::Dispatch::new()
|
|
||||||
.format(|out, message, record| {
|
|
||||||
out.finish(format_args!(
|
|
||||||
"[{} {} {}] {}",
|
|
||||||
humantime::format_rfc3339_seconds(SystemTime::now()),
|
|
||||||
record.level(),
|
|
||||||
record.target(),
|
|
||||||
message
|
|
||||||
))
|
|
||||||
})
|
|
||||||
.level(log::LevelFilter::Debug)
|
|
||||||
.chain(std::io::stdout())
|
|
||||||
.chain(fern::log_file("output.log")?)
|
|
||||||
.apply()?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
setup_logger().expect("failed to initialize logger");
|
setup_logger().expect("failed to initialize logger");
|
||||||
println!("sat-rs embedded examples TMTC client");
|
println!("-- STM32F3 TMTC client --");
|
||||||
|
|
||||||
let cli = Cli::parse();
|
let cli = Cli::parse();
|
||||||
let mut config_file =
|
let config = embedded_client::Config::new_from_file();
|
||||||
File::open(Path::new("config.toml")).expect("opening config.toml file failed");
|
|
||||||
let mut toml_str = String::new();
|
|
||||||
config_file
|
|
||||||
.read_to_string(&mut toml_str)
|
|
||||||
.expect("reading config.toml file failed");
|
|
||||||
let config: Config = toml::from_str(&toml_str).expect("parsing config.toml file failed");
|
|
||||||
println!("Connecting to serial port {}", config.interface.serial_port);
|
|
||||||
|
|
||||||
let serial = serialport::new(config.interface.serial_port, 115200)
|
let serial = serialport::new(config.interface.serial_port, 115200)
|
||||||
.open()
|
.open()
|
||||||
@@ -69,8 +29,7 @@ fn main() {
|
|||||||
let mut transport = PacketTransportSerialCobs::new(serial, CobsDecoderOwned::new(1024));
|
let mut transport = PacketTransportSerialCobs::new(serial, CobsDecoderOwned::new(1024));
|
||||||
|
|
||||||
if cli.ping {
|
if cli.ping {
|
||||||
let request = Request::Ping;
|
let tc = create_stm32f3_tc(&embedded_models::stm32f3::Request::Ping);
|
||||||
let tc = create_stm32f3_tc(&request);
|
|
||||||
log::info!(
|
log::info!(
|
||||||
"Sending ping request with TC ID: {:#010x}",
|
"Sending ping request with TC ID: {:#010x}",
|
||||||
tc.ccsds_packet_id_and_psc().raw()
|
tc.ccsds_packet_id_and_psc().raw()
|
||||||
@@ -79,7 +38,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(freq_ms) = cli.set_led_frequency {
|
if let Some(freq_ms) = cli.set_led_frequency {
|
||||||
let request = Request::ChangeBlinkFrequency(Duration::from_millis(freq_ms as u64));
|
let request = stm32f3::Request::ChangeBlinkFrequency(Duration::from_millis(freq_ms as u64));
|
||||||
let tc = create_stm32f3_tc(&request);
|
let tc = create_stm32f3_tc(&request);
|
||||||
log::info!(
|
log::info!(
|
||||||
"Sending change blink frequency request {:?} with TC ID: {:#010x}",
|
"Sending change blink frequency request {:?} with TC ID: {:#010x}",
|
||||||
@@ -100,7 +59,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_stm32f3_tc(request: &Request) -> CcsdsPacketCreatorOwned {
|
fn create_stm32f3_tc(request: &stm32f3::Request) -> CcsdsPacketCreatorOwned {
|
||||||
let req_raw = postcard::to_allocvec(&request).unwrap();
|
let req_raw = postcard::to_allocvec(&request).unwrap();
|
||||||
let sp_header = SpHeader::new_from_apid(satrs_stm32f3_disco_rtic::APID);
|
let sp_header = SpHeader::new_from_apid(satrs_stm32f3_disco_rtic::APID);
|
||||||
CcsdsPacketCreatorOwned::new_tc_with_checksum(sp_header, &req_raw).unwrap()
|
CcsdsPacketCreatorOwned::new_tc_with_checksum(sp_header, &req_raw).unwrap()
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
use std::{fs::File, io::Read, path::Path, time::Duration};
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
|
use cobs::CobsDecoderOwned;
|
||||||
|
use embedded_client::setup_logger;
|
||||||
|
use embedded_models::{stm32f3, stm32h7};
|
||||||
|
use spacepackets::{CcsdsPacketCreatorOwned, CcsdsPacketReader, SpHeader};
|
||||||
|
use tmtc_utils::transport::serial::PacketTransportSerialCobs;
|
||||||
|
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
struct Cli {
|
||||||
|
#[arg(short, long)]
|
||||||
|
ping: bool,
|
||||||
|
|
||||||
|
/// Set frequency in milliseconds.
|
||||||
|
#[arg(short, long)]
|
||||||
|
set_led_frequency: Option<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
setup_logger().expect("failed to initialize logger");
|
||||||
|
println!("-- STM32H7 TMTC client --");
|
||||||
|
let cli = Cli::parse();
|
||||||
|
let config = embedded_client::Config::new_from_file();
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
if cli.ping {
|
||||||
|
let tc = create_stm32f3_tc(&embedded_models::stm32f3::Request::Ping);
|
||||||
|
log::info!(
|
||||||
|
"Sending ping request with TC ID: {:#010x}",
|
||||||
|
tc.ccsds_packet_id_and_psc().raw()
|
||||||
|
);
|
||||||
|
transport.send(&tc.to_vec()).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(freq_ms) = cli.set_led_frequency {
|
||||||
|
let request = stm32f3::Request::ChangeBlinkFrequency(Duration::from_millis(freq_ms as u64));
|
||||||
|
let tc = create_stm32f3_tc(&request);
|
||||||
|
log::info!(
|
||||||
|
"Sending change blink frequency request {:?} with TC ID: {:#010x}",
|
||||||
|
request,
|
||||||
|
tc.ccsds_packet_id_and_psc().raw()
|
||||||
|
);
|
||||||
|
transport.send(&tc.to_vec()).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
log::info!("Waiting for response...");
|
||||||
|
loop {
|
||||||
|
transport
|
||||||
|
.receive(|packet: &[u8]| {
|
||||||
|
let reader = CcsdsPacketReader::new_with_checksum(packet);
|
||||||
|
log::info!("Received packet: {:?}", reader);
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_stm32h7_tc(request: &stm32h7::Request) -> CcsdsPacketCreatorOwned {
|
||||||
|
let req_raw = postcard::to_allocvec(&request).unwrap();
|
||||||
|
let sp_header = SpHeader::new_from_apid(satrs_stm32f3_disco_rtic::APID);
|
||||||
|
CcsdsPacketCreatorOwned::new_tc_with_checksum(sp_header, &req_raw).unwrap()
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
use std::{fs::File, io::Read as _, path::Path, time::SystemTime};
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Deserialize)]
|
||||||
|
pub struct Config {
|
||||||
|
pub interface: Interface,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Deserialize)]
|
||||||
|
pub struct Interface {
|
||||||
|
pub serial_port: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
pub fn new_from_file() -> Self {
|
||||||
|
let mut config_file =
|
||||||
|
File::open(Path::new("config.toml")).expect("opening config.toml file failed");
|
||||||
|
let mut toml_str = String::new();
|
||||||
|
config_file
|
||||||
|
.read_to_string(&mut toml_str)
|
||||||
|
.expect("reading config.toml file failed");
|
||||||
|
let config: Config = toml::from_str(&toml_str).expect("parsing config.toml file failed");
|
||||||
|
println!("Connecting to serial port {}", config.interface.serial_port);
|
||||||
|
config
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn setup_logger() -> Result<(), fern::InitError> {
|
||||||
|
fern::Dispatch::new()
|
||||||
|
.format(|out, message, record| {
|
||||||
|
out.finish(format_args!(
|
||||||
|
"[{} {} {}] {}",
|
||||||
|
humantime::format_rfc3339_seconds(SystemTime::now()),
|
||||||
|
record.level(),
|
||||||
|
record.target(),
|
||||||
|
message
|
||||||
|
))
|
||||||
|
})
|
||||||
|
.level(log::LevelFilter::Debug)
|
||||||
|
.chain(std::io::stdout())
|
||||||
|
.chain(fern::log_file("output.log")?)
|
||||||
|
.apply()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
[package]
|
||||||
|
name = "embedded-models"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
serde = { version = "1", default-features = false }
|
||||||
|
defmt = { version = "1", optional = true }
|
||||||
|
spacepackets = { version = "0.17", default-features = false, features = ["defmt", "serde"] }
|
||||||
|
postcard = { version = "1", features = ["defmt"] }
|
||||||
|
arbitrary-int = "2"
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
#![no_std]
|
||||||
|
|
||||||
|
use spacepackets::{
|
||||||
|
CcsdsPacketCreationError, CcsdsPacketCreatorWithReservedData, CcsdsPacketIdAndPsc,
|
||||||
|
SpacePacketHeader, ccsds_packet_len_for_user_data_len_with_checksum,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub mod stm32f3 {
|
||||||
|
use arbitrary_int::u11;
|
||||||
|
use core::time::Duration;
|
||||||
|
|
||||||
|
pub const PUS_APID: u11 = u11::new(0x02);
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub enum Request {
|
||||||
|
Ping,
|
||||||
|
ChangeBlinkFrequency(Duration),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub enum Response {
|
||||||
|
Ok,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This might look like a duplication, but we intentionally keep those separate so they can
|
||||||
|
/// change independently.
|
||||||
|
pub mod stm32h7 {
|
||||||
|
use arbitrary_int::u11;
|
||||||
|
use core::time::Duration;
|
||||||
|
|
||||||
|
pub const PUS_APID: u11 = u11::new(0x03);
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub enum Request {
|
||||||
|
Ping,
|
||||||
|
ChangeBlinkFrequency(Duration),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub enum Response {
|
||||||
|
Ok,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub struct TmHeader {
|
||||||
|
pub tc_packet_id: Option<CcsdsPacketIdAndPsc>,
|
||||||
|
pub uptime_millis: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tm_size<Response: serde::Serialize>(tm_header: &TmHeader, response: &Response) -> usize {
|
||||||
|
ccsds_packet_len_for_user_data_len_with_checksum(
|
||||||
|
postcard::experimental::serialized_size(tm_header).unwrap()
|
||||||
|
+ postcard::experimental::serialized_size(response).unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_tm_packet<Response: serde::Serialize>(
|
||||||
|
buf: &mut [u8],
|
||||||
|
sp_header: SpacePacketHeader,
|
||||||
|
tm_header: TmHeader,
|
||||||
|
response: Response,
|
||||||
|
) -> Result<usize, CcsdsPacketCreationError> {
|
||||||
|
let packet_data_size = postcard::experimental::serialized_size(&tm_header).unwrap()
|
||||||
|
+ postcard::experimental::serialized_size(&response).unwrap();
|
||||||
|
let mut creator =
|
||||||
|
CcsdsPacketCreatorWithReservedData::new_tm_with_checksum(sp_header, packet_data_size, buf)?;
|
||||||
|
|
||||||
|
let current_index = postcard::to_slice(&tm_header, creator.packet_data_mut())
|
||||||
|
.unwrap()
|
||||||
|
.len();
|
||||||
|
postcard::to_slice(&response, &mut creator.packet_data_mut()[current_index..]).unwrap();
|
||||||
|
Ok(creator.finish())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {}
|
||||||
+15
-1
@@ -592,6 +592,17 @@ dependencies = [
|
|||||||
"embedded-io",
|
"embedded-io",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "embedded-models"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"arbitrary-int 2.0.0",
|
||||||
|
"defmt 1.0.1",
|
||||||
|
"postcard",
|
||||||
|
"serde",
|
||||||
|
"spacepackets",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "embedded-storage"
|
name = "embedded-storage"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
@@ -942,6 +953,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24"
|
checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cobs 0.3.0",
|
"cobs 0.3.0",
|
||||||
|
"defmt 1.0.1",
|
||||||
"heapless 0.7.17",
|
"heapless 0.7.17",
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
@@ -1139,6 +1151,7 @@ dependencies = [
|
|||||||
"defmt-test",
|
"defmt-test",
|
||||||
"embassy-stm32",
|
"embassy-stm32",
|
||||||
"embedded-hal 1.0.0",
|
"embedded-hal 1.0.0",
|
||||||
|
"embedded-models",
|
||||||
"enumset",
|
"enumset",
|
||||||
"heapless 0.9.1",
|
"heapless 0.9.1",
|
||||||
"panic-probe",
|
"panic-probe",
|
||||||
@@ -1245,7 +1258,8 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "spacepackets"
|
name = "spacepackets"
|
||||||
version = "0.17.0"
|
version = "0.17.0"
|
||||||
source = "git+https://egit.irs.uni-stuttgart.de/rust/spacepackets.git#2bc61677105765e69cc96bb1ff9960557c00fa8e"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "94979a990b4333f43667cba9fe9e72b9c4e9ada82e09fbe8fb250844358590cc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arbitrary-int 2.0.0",
|
"arbitrary-int 2.0.0",
|
||||||
"bitbybit",
|
"bitbybit",
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ default-run = "satrs-stm32f3-disco-rtic"
|
|||||||
# 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
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
embedded-models = { path = "../models", features = ["defmt"] }
|
||||||
cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
|
cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
|
||||||
cortex-m-rt = "0.7"
|
cortex-m-rt = "0.7"
|
||||||
defmt = "1"
|
defmt = "1"
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
#![no_main]
|
#![no_main]
|
||||||
#![no_std]
|
#![no_std]
|
||||||
|
|
||||||
|
use defmt_rtt as _;
|
||||||
|
|
||||||
use arbitrary_int::u11;
|
use arbitrary_int::u11;
|
||||||
use core::time::Duration;
|
|
||||||
use embassy_stm32::gpio::Output;
|
use embassy_stm32::gpio::Output;
|
||||||
use spacepackets::{
|
|
||||||
ccsds_packet_len_for_user_data_len_with_checksum, CcsdsPacketCreationError,
|
|
||||||
CcsdsPacketCreatorWithReservedData, CcsdsPacketIdAndPsc, SpacePacketHeader,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const APID: u11 = u11::new(0x02);
|
pub const APID: u11 = u11::new(0x02);
|
||||||
|
|
||||||
@@ -40,49 +37,6 @@ impl Direction {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, defmt::Format, serde::Serialize, serde::Deserialize)]
|
|
||||||
pub enum Request {
|
|
||||||
Ping,
|
|
||||||
ChangeBlinkFrequency(Duration),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, defmt::Format, serde::Serialize, serde::Deserialize)]
|
|
||||||
pub struct TmHeader {
|
|
||||||
pub tc_packet_id: Option<CcsdsPacketIdAndPsc>,
|
|
||||||
pub uptime_millis: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, defmt::Format, serde::Serialize, serde::Deserialize)]
|
|
||||||
pub enum Response {
|
|
||||||
CommandDone,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn tm_size(tm_header: &TmHeader, response: &Response) -> usize {
|
|
||||||
ccsds_packet_len_for_user_data_len_with_checksum(
|
|
||||||
postcard::experimental::serialized_size(tm_header).unwrap()
|
|
||||||
+ postcard::experimental::serialized_size(response).unwrap(),
|
|
||||||
)
|
|
||||||
.unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_tm_packet(
|
|
||||||
buf: &mut [u8],
|
|
||||||
sp_header: SpacePacketHeader,
|
|
||||||
tm_header: TmHeader,
|
|
||||||
response: Response,
|
|
||||||
) -> Result<usize, CcsdsPacketCreationError> {
|
|
||||||
let packet_data_size = postcard::experimental::serialized_size(&tm_header).unwrap()
|
|
||||||
+ postcard::experimental::serialized_size(&response).unwrap();
|
|
||||||
let mut creator =
|
|
||||||
CcsdsPacketCreatorWithReservedData::new_tm_with_checksum(sp_header, packet_data_size, buf)?;
|
|
||||||
|
|
||||||
let current_index = postcard::to_slice(&tm_header, creator.packet_data_mut())
|
|
||||||
.unwrap()
|
|
||||||
.len();
|
|
||||||
postcard::to_slice(&response, &mut creator.packet_data_mut()[current_index..]).unwrap();
|
|
||||||
Ok(creator.finish())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Leds {
|
pub struct Leds {
|
||||||
pub north: Output<'static>,
|
pub north: Output<'static>,
|
||||||
pub north_east: Output<'static>,
|
pub north_east: Output<'static>,
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
#![no_std]
|
#![no_std]
|
||||||
#![no_main]
|
#![no_main]
|
||||||
use arbitrary_int::{u11, u14};
|
use arbitrary_int::u14;
|
||||||
use cortex_m_semihosting::debug::{self, EXIT_FAILURE, EXIT_SUCCESS};
|
use cortex_m_semihosting::debug::{self, EXIT_FAILURE, EXIT_SUCCESS};
|
||||||
use satrs_stm32f3_disco_rtic::{create_tm_packet, tm_size, CcsdsPacketId, Request, Response};
|
use embedded_models::{create_tm_packet, stm32f3, tm_size, TmHeader};
|
||||||
use spacepackets::{CcsdsPacketCreationError, SpHeader};
|
use spacepackets::{CcsdsPacketCreationError, CcsdsPacketIdAndPsc, SpHeader};
|
||||||
|
|
||||||
use defmt_rtt as _; // global logger
|
use defmt_rtt as _; // global logger
|
||||||
|
|
||||||
@@ -23,8 +23,6 @@ const TX_HANDLER_FREQ_MS: u32 = 20;
|
|||||||
const MAX_TC_LEN: usize = 128;
|
const MAX_TC_LEN: usize = 128;
|
||||||
const MAX_TM_LEN: usize = 128;
|
const MAX_TM_LEN: usize = 128;
|
||||||
|
|
||||||
pub const PUS_APID: u11 = u11::new(0x02);
|
|
||||||
|
|
||||||
// This is the predictable maximum overhead of the COBS encoding scheme.
|
// This is the predictable maximum overhead of the COBS encoding scheme.
|
||||||
// It is simply the maximum packet lenght dividied by 254 rounded up.
|
// It is simply the maximum packet lenght dividied by 254 rounded up.
|
||||||
const COBS_TM_OVERHEAD: usize = cobs::max_encoding_overhead(MAX_TM_LEN);
|
const COBS_TM_OVERHEAD: usize = cobs::max_encoding_overhead(MAX_TM_LEN);
|
||||||
@@ -47,8 +45,8 @@ pub enum TmSendError {
|
|||||||
|
|
||||||
#[derive(Debug, defmt::Format)]
|
#[derive(Debug, defmt::Format)]
|
||||||
pub struct RequestWithTcId {
|
pub struct RequestWithTcId {
|
||||||
pub request: Request,
|
pub request: stm32f3::Request,
|
||||||
pub tc_id: CcsdsPacketId,
|
pub tc_id: CcsdsPacketIdAndPsc,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[app(device = embassy_stm32)]
|
#[app(device = embassy_stm32)]
|
||||||
@@ -57,12 +55,13 @@ mod app {
|
|||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use arbitrary_int::u14;
|
use arbitrary_int::u14;
|
||||||
|
use embedded_models::stm32f3::{Request, Response};
|
||||||
use rtic::Mutex;
|
use rtic::Mutex;
|
||||||
use rtic_sync::{
|
use rtic_sync::{
|
||||||
channel::{Receiver, Sender},
|
channel::{Receiver, Sender},
|
||||||
make_channel,
|
make_channel,
|
||||||
};
|
};
|
||||||
use satrs_stm32f3_disco_rtic::{CcsdsPacketId, LedPinSet, Request, Response};
|
use satrs_stm32f3_disco_rtic::LedPinSet;
|
||||||
use spacepackets::CcsdsPacketReader;
|
use spacepackets::CcsdsPacketReader;
|
||||||
|
|
||||||
systick_monotonic!(Mono, 1000);
|
systick_monotonic!(Mono, 1000);
|
||||||
@@ -200,7 +199,7 @@ mod app {
|
|||||||
Ok(packet) => {
|
Ok(packet) => {
|
||||||
let packet_id = packet.packet_id();
|
let packet_id = packet.packet_id();
|
||||||
let psc = packet.psc();
|
let psc = packet.psc();
|
||||||
let tc_packet_id = CcsdsPacketId { packet_id, psc };
|
let tc_packet_id = CcsdsPacketIdAndPsc { packet_id, psc };
|
||||||
if let Ok(request) =
|
if let Ok(request) =
|
||||||
postcard::from_bytes::<Request>(packet.packet_data())
|
postcard::from_bytes::<Request>(packet.packet_data())
|
||||||
{
|
{
|
||||||
@@ -260,17 +259,17 @@ mod app {
|
|||||||
|
|
||||||
fn handle_ping_request(
|
fn handle_ping_request(
|
||||||
cx: &mut req_handler::Context,
|
cx: &mut req_handler::Context,
|
||||||
tc_packet_id: CcsdsPacketId,
|
tc_packet_id: CcsdsPacketIdAndPsc,
|
||||||
) -> Result<(), TmSendError> {
|
) -> Result<(), TmSendError> {
|
||||||
defmt::info!("Received PUS ping telecommand, sending ping reply");
|
defmt::info!("Received PUS ping telecommand, sending ping reply");
|
||||||
send_tm(tc_packet_id, Response::CommandDone, *cx.local.seq_count)?;
|
send_tm(tc_packet_id, Response::Ok, *cx.local.seq_count)?;
|
||||||
*cx.local.seq_count = cx.local.seq_count.wrapping_add(u14::new(1));
|
*cx.local.seq_count = cx.local.seq_count.wrapping_add(u14::new(1));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_change_blink_frequency_request(
|
fn handle_change_blink_frequency_request(
|
||||||
cx: &mut req_handler::Context,
|
cx: &mut req_handler::Context,
|
||||||
tc_packet_id: CcsdsPacketId,
|
tc_packet_id: CcsdsPacketIdAndPsc,
|
||||||
duration: Duration,
|
duration: Duration,
|
||||||
) -> Result<(), TmSendError> {
|
) -> Result<(), TmSendError> {
|
||||||
defmt::info!(
|
defmt::info!(
|
||||||
@@ -280,21 +279,21 @@ mod app {
|
|||||||
cx.shared
|
cx.shared
|
||||||
.blink_freq
|
.blink_freq
|
||||||
.lock(|blink_freq| *blink_freq = duration);
|
.lock(|blink_freq| *blink_freq = duration);
|
||||||
send_tm(tc_packet_id, Response::CommandDone, *cx.local.seq_count)?;
|
send_tm(tc_packet_id, Response::Ok, *cx.local.seq_count)?;
|
||||||
*cx.local.seq_count = cx.local.seq_count.wrapping_add(u14::new(1));
|
*cx.local.seq_count = cx.local.seq_count.wrapping_add(u14::new(1));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_tm(
|
fn send_tm(
|
||||||
tc_packet_id: CcsdsPacketId,
|
tc_packet_id: CcsdsPacketIdAndPsc,
|
||||||
response: Response,
|
response: stm32f3::Response,
|
||||||
current_seq_count: u14,
|
current_seq_count: u14,
|
||||||
) -> Result<(), TmSendError> {
|
) -> Result<(), TmSendError> {
|
||||||
let sp_header = SpHeader::new_for_unseg_tc(PUS_APID, current_seq_count, 0);
|
let sp_header = SpHeader::new_for_unseg_tc(stm32f3::PUS_APID, current_seq_count, 0);
|
||||||
let tm_header = satrs_stm32f3_disco_rtic::TmHeader {
|
let tm_header = TmHeader {
|
||||||
tc_packet_id: Some(tc_packet_id),
|
tc_packet_id: Some(tc_packet_id),
|
||||||
uptime_millis: Mono::now().duration_since_epoch().to_millis(),
|
uptime_millis: Mono::now().duration_since_epoch().to_millis() as u64,
|
||||||
};
|
};
|
||||||
let mut tm_packet = TmPacket::new();
|
let mut tm_packet = TmPacket::new();
|
||||||
let tm_size = tm_size(&tm_header, &response);
|
let tm_size = tm_size(&tm_header, &response);
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
|
|
||||||
runner = "probe-rs run --chip STM32H743ZITx"
|
|
||||||
# runner = ["probe-rs", "run", "--chip", "$CHIP", "--log-format", "{L} {s}"]
|
|
||||||
|
|
||||||
rustflags = [
|
|
||||||
"-C", "linker=flip-link",
|
|
||||||
"-C", "link-arg=-Tlink.x",
|
|
||||||
"-C", "link-arg=-Tdefmt.x",
|
|
||||||
# This is needed if your flash or ram addresses are not aligned to 0x10000 in memory.x
|
|
||||||
# See https://github.com/rust-embedded/cortex-m-quickstart/pull/95
|
|
||||||
"-C", "link-arg=--nmagic",
|
|
||||||
# Can be useful for debugging.
|
|
||||||
# "-Clink-args=-Map=app.map"
|
|
||||||
]
|
|
||||||
|
|
||||||
[build]
|
|
||||||
# (`thumbv6m-*` is compatible with all ARM Cortex-M chips but using the right
|
|
||||||
# target improves performance)
|
|
||||||
# target = "thumbv6m-none-eabi" # Cortex-M0 and Cortex-M0+
|
|
||||||
# target = "thumbv7m-none-eabi" # Cortex-M3
|
|
||||||
# target = "thumbv7em-none-eabi" # Cortex-M4 and Cortex-M7 (no FPU)
|
|
||||||
target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU)
|
|
||||||
|
|
||||||
[alias]
|
|
||||||
rb = "run --bin"
|
|
||||||
rrb = "run --release --bin"
|
|
||||||
|
|
||||||
[env]
|
|
||||||
DEFMT_LOG = "info"
|
|
||||||
+644
-593
File diff suppressed because it is too large
Load Diff
@@ -14,37 +14,28 @@ name = "integration"
|
|||||||
harness = false
|
harness = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
embedded-models = { path = "../models", features = ["defmt"] }
|
||||||
|
|
||||||
cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
|
cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
|
||||||
|
arbitrary-int = "2"
|
||||||
cortex-m-rt = "0.7"
|
cortex-m-rt = "0.7"
|
||||||
defmt = "1"
|
defmt = "1"
|
||||||
defmt-brtt = { version = "0.1", default-features = false, features = ["rtt"] }
|
defmt-rtt = "1"
|
||||||
panic-probe = { version = "1", features = ["print-defmt"] }
|
panic-probe = { version = "1", features = ["print-defmt"] }
|
||||||
cortex-m-semihosting = "0.5.0"
|
embedded-alloc = "0.7"
|
||||||
# TODO: Replace with embassy-hal.
|
static_cell = "2"
|
||||||
stm32h7xx-hal = { version="0.16", features= ["stm32h743v", "ethernet"] }
|
rtic = { version = "2", features = ["thumbv7-backend"] }
|
||||||
embedded-alloc = "0.6"
|
spacepackets = { version = "0.17", default-features = false, features = ["defmt"] }
|
||||||
rtic-sync = { version = "1", features = ["defmt-03"] }
|
postcard = "1"
|
||||||
|
|
||||||
[dependencies.smoltcp]
|
embassy-stm32 = { git = "https://github.com/embassy-rs/embassy.git", rev = "dd8e4c14e53f088bae27c5d841ab7a4fa338a52c", version = "0.6", features = ["stm32h743zi", "memory-x", "defmt", "time-driver-any"]}
|
||||||
version = "0.12"
|
|
||||||
default-features = false
|
|
||||||
features = ["medium-ethernet", "proto-ipv4", "socket-raw", "socket-dhcpv4", "socket-udp", "defmt"]
|
|
||||||
|
|
||||||
[dependencies.rtic]
|
embassy-time = { git = "https://github.com/embassy-rs/embassy.git", rev = "dd8e4c14e53f088bae27c5d841ab7a4fa338a52c", version = "0.5", features = ["defmt-timestamp-uptime-ms", "generic-queue-16"] }
|
||||||
version = "2"
|
embassy-net = { git = "https://github.com/embassy-rs/embassy.git", rev = "dd8e4c14e53f088bae27c5d841ab7a4fa338a52c", version = "0.9", features = ["medium-ethernet", "proto-ipv4", "tcp", "udp", "auto-icmp-echo-reply", "dhcpv4", "defmt"] }
|
||||||
features = ["thumbv7-backend"]
|
embassy-sync = { git = "https://github.com/embassy-rs/embassy.git", rev = "dd8e4c14e53f088bae27c5d841ab7a4fa338a52c" }
|
||||||
|
|
||||||
[dependencies.rtic-monotonics]
|
|
||||||
version = "2"
|
|
||||||
features = ["cortex-m-systick"]
|
|
||||||
|
|
||||||
[dependencies.satrs]
|
|
||||||
path = "../../satrs"
|
|
||||||
default-features = false
|
|
||||||
features = ["defmt", "heapless"]
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
defmt-test = "0.4"
|
defmt-test = "0.5"
|
||||||
|
|
||||||
# cargo build/run
|
# cargo build/run
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,14 @@
|
|||||||
|
use std::path::PathBuf;
|
||||||
|
use std::{env, fs};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let manifest_dir = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap());
|
||||||
|
let cargo_dir = manifest_dir.parent().unwrap().join(".cargo");
|
||||||
|
let config = cargo_dir.join("config.toml");
|
||||||
|
let config_template = cargo_dir.join("config.toml.template");
|
||||||
|
|
||||||
|
if !config.exists() && config_template.exists() {
|
||||||
|
fs::create_dir_all(&cargo_dir).unwrap();
|
||||||
|
fs::copy(&config_template, &config).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
/venv
|
|
||||||
/.tmtc-history.txt
|
|
||||||
/log
|
|
||||||
/.idea/*
|
|
||||||
!/.idea/runConfigurations
|
|
||||||
|
|
||||||
/seqcnt.txt
|
|
||||||
/tmtc_conf.json
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"com_if": "udp",
|
|
||||||
"tcpip_udp_port": 7301
|
|
||||||
}
|
|
||||||
@@ -1,305 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""Example client for the sat-rs example application"""
|
|
||||||
import struct
|
|
||||||
import logging
|
|
||||||
import sys
|
|
||||||
import time
|
|
||||||
from typing import Any, Optional, cast
|
|
||||||
from prompt_toolkit.history import FileHistory, History
|
|
||||||
from spacepackets.ecss.tm import CdsShortTimestamp
|
|
||||||
|
|
||||||
import tmtccmd
|
|
||||||
from spacepackets.ecss import PusTelemetry, PusTelecommand, PusTm, PusVerificator
|
|
||||||
from spacepackets.ecss.pus_17_test import Service17Tm
|
|
||||||
from spacepackets.ecss.pus_1_verification import UnpackParams, Service1Tm
|
|
||||||
|
|
||||||
from tmtccmd import TcHandlerBase, ProcedureParamsWrapper
|
|
||||||
from tmtccmd.core.base import BackendRequest
|
|
||||||
from tmtccmd.core.ccsds_backend import QueueWrapper
|
|
||||||
from tmtccmd.logging import add_colorlog_console_logger
|
|
||||||
from tmtccmd.pus import VerificationWrapper
|
|
||||||
from tmtccmd.tmtc import CcsdsTmHandler, SpecificApidHandlerBase
|
|
||||||
from tmtccmd.com import ComInterface
|
|
||||||
from tmtccmd.config import (
|
|
||||||
CmdTreeNode,
|
|
||||||
default_json_path,
|
|
||||||
SetupParams,
|
|
||||||
HookBase,
|
|
||||||
params_to_procedure_conversion,
|
|
||||||
)
|
|
||||||
from tmtccmd.config.com import SerialCfgWrapper
|
|
||||||
from tmtccmd.config import PreArgsParsingWrapper, SetupWrapper
|
|
||||||
from tmtccmd.logging.pus import (
|
|
||||||
RegularTmtcLogWrapper,
|
|
||||||
RawTmtcTimedLogWrapper,
|
|
||||||
TimedLogWhen,
|
|
||||||
)
|
|
||||||
from tmtccmd.tmtc import (
|
|
||||||
TcQueueEntryType,
|
|
||||||
ProcedureWrapper,
|
|
||||||
TcProcedureType,
|
|
||||||
FeedWrapper,
|
|
||||||
SendCbParams,
|
|
||||||
DefaultPusQueueHelper,
|
|
||||||
)
|
|
||||||
from tmtccmd.pus.s5_fsfw_event import Service5Tm
|
|
||||||
from spacepackets.seqcount import FileSeqCountProvider, PusFileSeqCountProvider
|
|
||||||
from tmtccmd.util.obj_id import ObjectIdDictT
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger()
|
|
||||||
|
|
||||||
EXAMPLE_PUS_APID = 0x02
|
|
||||||
|
|
||||||
|
|
||||||
class SatRsConfigHook(HookBase):
|
|
||||||
def __init__(self, json_cfg_path: str):
|
|
||||||
super().__init__(json_cfg_path)
|
|
||||||
|
|
||||||
def get_communication_interface(self, com_if_key: str) -> Optional[ComInterface]:
|
|
||||||
from tmtccmd.config.com import (
|
|
||||||
create_com_interface_default,
|
|
||||||
create_com_interface_cfg_default,
|
|
||||||
)
|
|
||||||
|
|
||||||
assert self.cfg_path is not None
|
|
||||||
cfg = create_com_interface_cfg_default(
|
|
||||||
com_if_key=com_if_key,
|
|
||||||
json_cfg_path=self.cfg_path,
|
|
||||||
space_packet_ids=None,
|
|
||||||
)
|
|
||||||
if cfg is None:
|
|
||||||
raise ValueError(
|
|
||||||
f"No valid configuration could be retrieved for the COM IF with key {com_if_key}"
|
|
||||||
)
|
|
||||||
if cfg.com_if_key == "serial_cobs":
|
|
||||||
cfg = cast(SerialCfgWrapper, cfg)
|
|
||||||
cfg.serial_cfg.serial_timeout = 0.5
|
|
||||||
return create_com_interface_default(cfg)
|
|
||||||
|
|
||||||
def get_command_definitions(self) -> CmdTreeNode:
|
|
||||||
"""This function should return the root node of the command definition tree."""
|
|
||||||
return create_cmd_definition_tree()
|
|
||||||
|
|
||||||
def get_cmd_history(self) -> Optional[History]:
|
|
||||||
"""Optionlly return a history class for the past command paths which will be used
|
|
||||||
when prompting a command path from the user in CLI mode."""
|
|
||||||
return FileHistory(".tmtc-history.txt")
|
|
||||||
|
|
||||||
def get_object_ids(self) -> ObjectIdDictT:
|
|
||||||
from tmtccmd.config.objects import get_core_object_ids
|
|
||||||
|
|
||||||
return get_core_object_ids()
|
|
||||||
|
|
||||||
|
|
||||||
def create_cmd_definition_tree() -> CmdTreeNode:
|
|
||||||
root_node = CmdTreeNode.root_node()
|
|
||||||
root_node.add_child(CmdTreeNode("ping", "Send PUS ping TC"))
|
|
||||||
root_node.add_child(CmdTreeNode("change_blink_freq", "Change blink frequency"))
|
|
||||||
return root_node
|
|
||||||
|
|
||||||
|
|
||||||
class PusHandler(SpecificApidHandlerBase):
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
file_logger: logging.Logger,
|
|
||||||
verif_wrapper: VerificationWrapper,
|
|
||||||
raw_logger: RawTmtcTimedLogWrapper,
|
|
||||||
):
|
|
||||||
super().__init__(EXAMPLE_PUS_APID, None)
|
|
||||||
self.file_logger = file_logger
|
|
||||||
self.raw_logger = raw_logger
|
|
||||||
self.verif_wrapper = verif_wrapper
|
|
||||||
|
|
||||||
def handle_tm(self, packet: bytes, _user_args: Any):
|
|
||||||
try:
|
|
||||||
pus_tm = PusTm.unpack(
|
|
||||||
packet, timestamp_len=CdsShortTimestamp.TIMESTAMP_SIZE
|
|
||||||
)
|
|
||||||
except ValueError as e:
|
|
||||||
_LOGGER.warning("Could not generate PUS TM object from raw data")
|
|
||||||
_LOGGER.warning(f"Raw Packet: [{packet.hex(sep=',')}], REPR: {packet!r}")
|
|
||||||
raise e
|
|
||||||
service = pus_tm.service
|
|
||||||
tm_packet = None
|
|
||||||
if service == 1:
|
|
||||||
tm_packet = Service1Tm.unpack(
|
|
||||||
data=packet, params=UnpackParams(CdsShortTimestamp.TIMESTAMP_SIZE, 1, 2)
|
|
||||||
)
|
|
||||||
res = self.verif_wrapper.add_tm(tm_packet)
|
|
||||||
if res is None:
|
|
||||||
_LOGGER.info(
|
|
||||||
f"Received Verification TM[{tm_packet.service}, {tm_packet.subservice}] "
|
|
||||||
f"with Request ID {tm_packet.tc_req_id.as_u32():#08x}"
|
|
||||||
)
|
|
||||||
_LOGGER.warning(
|
|
||||||
f"No matching telecommand found for {tm_packet.tc_req_id}"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
self.verif_wrapper.log_to_console(tm_packet, res)
|
|
||||||
self.verif_wrapper.log_to_file(tm_packet, res)
|
|
||||||
if service == 3:
|
|
||||||
_LOGGER.info("No handling for HK packets implemented")
|
|
||||||
_LOGGER.info(f"Raw packet: 0x[{packet.hex(sep=',')}]")
|
|
||||||
pus_tm = PusTelemetry.unpack(packet, CdsShortTimestamp.TIMESTAMP_SIZE)
|
|
||||||
if pus_tm.subservice == 25:
|
|
||||||
if len(pus_tm.source_data) < 8:
|
|
||||||
raise ValueError("No addressable ID in HK packet")
|
|
||||||
json_str = pus_tm.source_data[8:]
|
|
||||||
_LOGGER.info("received JSON string: " + json_str.decode("utf-8"))
|
|
||||||
if service == 5:
|
|
||||||
tm_packet = Service5Tm.unpack(packet, CdsShortTimestamp.TIMESTAMP_SIZE)
|
|
||||||
if service == 17:
|
|
||||||
tm_packet = Service17Tm.unpack(packet, CdsShortTimestamp.TIMESTAMP_SIZE)
|
|
||||||
if tm_packet.subservice == 2:
|
|
||||||
_LOGGER.info("Received Ping Reply TM[17,2]")
|
|
||||||
else:
|
|
||||||
_LOGGER.info(
|
|
||||||
f"Received Test Packet with unknown subservice {tm_packet.subservice}"
|
|
||||||
)
|
|
||||||
if tm_packet is None:
|
|
||||||
_LOGGER.info(
|
|
||||||
f"The service {service} is not implemented in Telemetry Factory"
|
|
||||||
)
|
|
||||||
tm_packet = PusTelemetry.unpack(packet, CdsShortTimestamp.TIMESTAMP_SIZE)
|
|
||||||
self.raw_logger.log_tm(pus_tm)
|
|
||||||
|
|
||||||
|
|
||||||
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))
|
|
||||||
return byte_string
|
|
||||||
|
|
||||||
|
|
||||||
class TcHandler(TcHandlerBase):
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
seq_count_provider: FileSeqCountProvider,
|
|
||||||
verif_wrapper: VerificationWrapper,
|
|
||||||
):
|
|
||||||
super(TcHandler, self).__init__()
|
|
||||||
self.seq_count_provider = seq_count_provider
|
|
||||||
self.verif_wrapper = verif_wrapper
|
|
||||||
self.queue_helper = DefaultPusQueueHelper(
|
|
||||||
queue_wrapper=QueueWrapper.empty(),
|
|
||||||
tc_sched_timestamp_len=7,
|
|
||||||
seq_cnt_provider=seq_count_provider,
|
|
||||||
pus_verificator=verif_wrapper.pus_verificator,
|
|
||||||
default_pus_apid=EXAMPLE_PUS_APID,
|
|
||||||
)
|
|
||||||
|
|
||||||
def send_cb(self, send_params: SendCbParams):
|
|
||||||
entry_helper = send_params.entry
|
|
||||||
if entry_helper.is_tc:
|
|
||||||
if entry_helper.entry_type == TcQueueEntryType.PUS_TC:
|
|
||||||
pus_tc_wrapper = entry_helper.to_pus_tc_entry()
|
|
||||||
pus_tc_wrapper.pus_tc.seq_count = (
|
|
||||||
self.seq_count_provider.get_and_increment()
|
|
||||||
)
|
|
||||||
self.verif_wrapper.add_tc(pus_tc_wrapper.pus_tc)
|
|
||||||
raw_tc = pus_tc_wrapper.pus_tc.pack()
|
|
||||||
_LOGGER.info(f"Sending {pus_tc_wrapper.pus_tc}")
|
|
||||||
send_params.com_if.send(raw_tc)
|
|
||||||
elif entry_helper.entry_type == TcQueueEntryType.LOG:
|
|
||||||
log_entry = entry_helper.to_log_entry()
|
|
||||||
_LOGGER.info(log_entry.log_str)
|
|
||||||
|
|
||||||
def queue_finished_cb(self, info: ProcedureWrapper):
|
|
||||||
if info.proc_type == TcProcedureType.TREE_COMMANDING:
|
|
||||||
def_proc = info.to_tree_commanding_procedure()
|
|
||||||
_LOGGER.info(f"Queue handling finished for command {def_proc.cmd_path}")
|
|
||||||
|
|
||||||
def feed_cb(self, info: ProcedureWrapper, wrapper: FeedWrapper):
|
|
||||||
q = self.queue_helper
|
|
||||||
q.queue_wrapper = wrapper.queue_wrapper
|
|
||||||
if info.proc_type == TcProcedureType.TREE_COMMANDING:
|
|
||||||
def_proc = info.to_tree_commanding_procedure()
|
|
||||||
cmd_path = def_proc.cmd_path
|
|
||||||
if cmd_path == "/ping":
|
|
||||||
q.add_log_cmd("Sending PUS ping telecommand")
|
|
||||||
q.add_pus_tc(PusTelecommand(service=17, subservice=1))
|
|
||||||
if cmd_path == "/change_blink_freq":
|
|
||||||
self.create_change_blink_freq_command(q)
|
|
||||||
|
|
||||||
def create_change_blink_freq_command(self, q: DefaultPusQueueHelper):
|
|
||||||
q.add_log_cmd("Changing blink frequency")
|
|
||||||
while True:
|
|
||||||
blink_freq = int(
|
|
||||||
input(
|
|
||||||
"Please specify new blink frequency in ms. Valid Range [2..10000]: "
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if blink_freq < 2 or blink_freq > 10000:
|
|
||||||
print(
|
|
||||||
"Invalid blink frequency. Please specify a value between 2 and 10000."
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
break
|
|
||||||
app_data = struct.pack("!I", blink_freq)
|
|
||||||
q.add_pus_tc(PusTelecommand(service=8, subservice=1, app_data=app_data))
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
add_colorlog_console_logger(_LOGGER)
|
|
||||||
tmtccmd.init_printout(False)
|
|
||||||
hook_obj = SatRsConfigHook(json_cfg_path=default_json_path())
|
|
||||||
parser_wrapper = PreArgsParsingWrapper()
|
|
||||||
parser_wrapper.create_default_parent_parser()
|
|
||||||
parser_wrapper.create_default_parser()
|
|
||||||
parser_wrapper.add_def_proc_args()
|
|
||||||
params = SetupParams()
|
|
||||||
post_args_wrapper = parser_wrapper.parse(hook_obj, params)
|
|
||||||
proc_wrapper = ProcedureParamsWrapper()
|
|
||||||
if post_args_wrapper.use_gui:
|
|
||||||
post_args_wrapper.set_params_without_prompts(proc_wrapper)
|
|
||||||
else:
|
|
||||||
post_args_wrapper.set_params_with_prompts(proc_wrapper)
|
|
||||||
params.apid = EXAMPLE_PUS_APID
|
|
||||||
setup_args = SetupWrapper(
|
|
||||||
hook_obj=hook_obj, setup_params=params, proc_param_wrapper=proc_wrapper
|
|
||||||
)
|
|
||||||
# Create console logger helper and file loggers
|
|
||||||
tmtc_logger = RegularTmtcLogWrapper()
|
|
||||||
file_logger = tmtc_logger.logger
|
|
||||||
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
|
|
||||||
tm_handler = PusHandler(file_logger, verification_wrapper, raw_logger)
|
|
||||||
ccsds_handler = CcsdsTmHandler(generic_handler=None)
|
|
||||||
ccsds_handler.add_apid_handler(tm_handler)
|
|
||||||
|
|
||||||
# Create TC handler
|
|
||||||
seq_count_provider = PusFileSeqCountProvider()
|
|
||||||
tc_handler = TcHandler(seq_count_provider, verification_wrapper)
|
|
||||||
tmtccmd.setup(setup_args=setup_args)
|
|
||||||
init_proc = params_to_procedure_conversion(setup_args.proc_param_wrapper)
|
|
||||||
tmtc_backend = tmtccmd.create_default_tmtc_backend(
|
|
||||||
setup_wrapper=setup_args,
|
|
||||||
tm_handler=ccsds_handler,
|
|
||||||
tc_handler=tc_handler,
|
|
||||||
init_procedure=init_proc,
|
|
||||||
)
|
|
||||||
tmtccmd.start(tmtc_backend=tmtc_backend, hook_obj=hook_obj)
|
|
||||||
try:
|
|
||||||
while True:
|
|
||||||
state = tmtc_backend.periodic_op(None)
|
|
||||||
if state.request == BackendRequest.TERMINATION_NO_ERROR:
|
|
||||||
sys.exit(0)
|
|
||||||
elif state.request == BackendRequest.DELAY_IDLE:
|
|
||||||
_LOGGER.info("TMTC Client in IDLE mode")
|
|
||||||
time.sleep(3.0)
|
|
||||||
elif state.request == BackendRequest.DELAY_LISTENER:
|
|
||||||
time.sleep(0.8)
|
|
||||||
elif state.request == BackendRequest.DELAY_CUSTOM:
|
|
||||||
if state.next_delay.total_seconds() <= 0.4:
|
|
||||||
time.sleep(state.next_delay.total_seconds())
|
|
||||||
else:
|
|
||||||
time.sleep(0.4)
|
|
||||||
elif state.request == BackendRequest.CALL_NEXT:
|
|
||||||
pass
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
tmtccmd == 8.0.1
|
|
||||||
# -e git+https://github.com/robamu-org/tmtccmd.git@main#egg=tmtccmd
|
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
[toolchain]
|
||||||
|
targets = ["thumbv7em-none-eabihf"]
|
||||||
@@ -5,51 +5,53 @@
|
|||||||
|
|
||||||
#![no_std]
|
#![no_std]
|
||||||
#![no_main]
|
#![no_main]
|
||||||
|
use rtic::app;
|
||||||
use satrs_stm32h7_nucleo_rtic as _;
|
use satrs_stm32h7_nucleo_rtic as _;
|
||||||
|
|
||||||
use stm32h7xx_hal::{block, prelude::*, timer::Timer};
|
#[app(device = embassy_stm32, peripherals = false, dispatchers = [SPI1])]
|
||||||
|
mod app {
|
||||||
|
use embassy_stm32::gpio;
|
||||||
|
|
||||||
use cortex_m_rt::entry;
|
#[shared]
|
||||||
|
struct Shared {}
|
||||||
|
|
||||||
#[entry]
|
#[local]
|
||||||
fn main() -> ! {
|
struct Local {}
|
||||||
defmt::println!("starting stm32h7 blinky example");
|
|
||||||
|
|
||||||
// Get access to the device specific peripherals from the peripheral access crate
|
#[init]
|
||||||
let dp = stm32h7xx_hal::stm32::Peripherals::take().unwrap();
|
fn init(_cx: init::Context) -> (Shared, Local) {
|
||||||
|
let p = embassy_stm32::init(Default::default());
|
||||||
|
defmt::info!("Hello World!");
|
||||||
|
// Configure gpio B pin 0 as a push-pull output.
|
||||||
|
let ld1 = gpio::Output::new(p.PB0, gpio::Level::High, gpio::Speed::Low);
|
||||||
|
let ld2 = gpio::Output::new(p.PB7, gpio::Level::High, gpio::Speed::Low);
|
||||||
|
let ld3 = gpio::Output::new(p.PB14, gpio::Level::High, gpio::Speed::Low);
|
||||||
|
|
||||||
// Take ownership over the RCC devices and convert them into the corresponding HAL structs
|
// Schedule the blinking task
|
||||||
let rcc = dp.RCC.constrain();
|
blink::spawn(ld1, ld2, ld3).ok();
|
||||||
|
|
||||||
let pwr = dp.PWR.constrain();
|
(Shared {}, Local {})
|
||||||
let pwrcfg = pwr.freeze();
|
}
|
||||||
|
|
||||||
// Freeze the configuration of all the clocks in the system and
|
#[task()]
|
||||||
// retrieve the Core Clock Distribution and Reset (CCDR) object
|
async fn blink(
|
||||||
let rcc = rcc.use_hse(8.MHz()).bypass_hse();
|
_cx: blink::Context,
|
||||||
let ccdr = rcc.freeze(pwrcfg, &dp.SYSCFG);
|
mut ld1: gpio::Output<'static>,
|
||||||
|
mut ld2: gpio::Output<'static>,
|
||||||
|
mut ld3: gpio::Output<'static>,
|
||||||
|
) {
|
||||||
|
loop {
|
||||||
|
defmt::info!("high");
|
||||||
|
ld1.set_high();
|
||||||
|
ld2.set_high();
|
||||||
|
ld3.set_high();
|
||||||
|
embassy_time::Timer::after_millis(500).await;
|
||||||
|
|
||||||
// Acquire the GPIOB peripheral
|
defmt::info!("low");
|
||||||
let gpiob = dp.GPIOB.split(ccdr.peripheral.GPIOB);
|
ld1.set_low();
|
||||||
|
ld2.set_low();
|
||||||
// Configure gpio B pin 0 as a push-pull output.
|
ld3.set_low();
|
||||||
let mut ld1 = gpiob.pb0.into_push_pull_output();
|
embassy_time::Timer::after_millis(500).await;
|
||||||
|
}
|
||||||
// Configure gpio B pin 7 as a push-pull output.
|
|
||||||
let mut ld2 = gpiob.pb7.into_push_pull_output();
|
|
||||||
|
|
||||||
// Configure gpio B pin 14 as a push-pull output.
|
|
||||||
let mut ld3 = gpiob.pb14.into_push_pull_output();
|
|
||||||
|
|
||||||
// Configure the timer to trigger an update every second
|
|
||||||
let mut timer = Timer::tim1(dp.TIM1, ccdr.peripheral.TIM1, &ccdr.clocks);
|
|
||||||
timer.start(1.Hz());
|
|
||||||
|
|
||||||
// Wait for the timer to trigger an update and change the state of the LED
|
|
||||||
loop {
|
|
||||||
ld1.toggle();
|
|
||||||
ld2.toggle();
|
|
||||||
ld3.toggle();
|
|
||||||
block!(timer.wait()).unwrap();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
#![no_main]
|
#![no_main]
|
||||||
#![no_std]
|
#![no_std]
|
||||||
|
|
||||||
use satrs_stm32h7_nucleo_rtic as _; // global logger + panicking-behavior + memory layout
|
// global logger + panicking-behavior + memory layout
|
||||||
|
use satrs_stm32h7_nucleo_rtic as _;
|
||||||
|
|
||||||
#[cortex_m_rt::entry]
|
#[cortex_m_rt::entry]
|
||||||
fn main() -> ! {
|
fn main() -> ! {
|
||||||
defmt::println!("Hello, world!");
|
loop {
|
||||||
|
defmt::println!("Hello, world!");
|
||||||
satrs_stm32h7_nucleo_rtic::exit()
|
cortex_m::asm::delay(100_000_000);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,8 @@
|
|||||||
#![no_main]
|
#![no_main]
|
||||||
#![no_std]
|
#![no_std]
|
||||||
|
|
||||||
use cortex_m_semihosting::debug;
|
use defmt_rtt as _;
|
||||||
|
use embassy_stm32 as _;
|
||||||
use defmt_brtt as _; // global logger
|
|
||||||
|
|
||||||
// TODO(5) adjust HAL import
|
|
||||||
use stm32h7xx_hal as _; // memory layout
|
|
||||||
|
|
||||||
use panic_probe as _;
|
use panic_probe as _;
|
||||||
|
|
||||||
// same panicking *behavior* as `panic-probe` but doesn't print a panic message
|
// same panicking *behavior* as `panic-probe` but doesn't print a panic message
|
||||||
@@ -17,14 +12,6 @@ fn panic() -> ! {
|
|||||||
cortex_m::asm::udf()
|
cortex_m::asm::udf()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Terminates the application and makes a semihosting-capable debug tool exit
|
|
||||||
/// with status code 0.
|
|
||||||
pub fn exit() -> ! {
|
|
||||||
loop {
|
|
||||||
debug::exit(debug::EXIT_SUCCESS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Hardfault handler.
|
/// Hardfault handler.
|
||||||
///
|
///
|
||||||
/// Terminates the application and makes a semihosting-capable debug tool exit
|
/// Terminates the application and makes a semihosting-capable debug tool exit
|
||||||
@@ -32,9 +19,7 @@ pub fn exit() -> ! {
|
|||||||
/// loop.
|
/// loop.
|
||||||
#[cortex_m_rt::exception]
|
#[cortex_m_rt::exception]
|
||||||
unsafe fn HardFault(_frame: &cortex_m_rt::ExceptionFrame) -> ! {
|
unsafe fn HardFault(_frame: &cortex_m_rt::ExceptionFrame) -> ! {
|
||||||
loop {
|
panic!("unexpected hard fault");
|
||||||
debug::exit(debug::EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// defmt-test 0.3.0 has the limitation that this `#[tests]` attribute can only be used
|
// defmt-test 0.3.0 has the limitation that this `#[tests]` attribute can only be used
|
||||||
|
|||||||
@@ -3,422 +3,215 @@
|
|||||||
extern crate alloc;
|
extern crate alloc;
|
||||||
|
|
||||||
use rtic::app;
|
use rtic::app;
|
||||||
use rtic_monotonics::systick::prelude::*;
|
|
||||||
use satrs::pool::{PoolAddr, PoolProvider, StaticHeaplessMemoryPool};
|
|
||||||
use satrs::static_subpool;
|
|
||||||
// global logger + panicking-behavior + memory layout
|
// global logger + panicking-behavior + memory layout
|
||||||
|
use embassy_stm32::bind_interrupts;
|
||||||
use satrs_stm32h7_nucleo_rtic as _;
|
use satrs_stm32h7_nucleo_rtic as _;
|
||||||
use smoltcp::socket::udp::UdpMetadata;
|
|
||||||
use smoltcp::socket::{dhcpv4, udp};
|
|
||||||
|
|
||||||
use core::mem::MaybeUninit;
|
use core::mem::MaybeUninit;
|
||||||
use embedded_alloc::LlffHeap as Heap;
|
use embedded_alloc::LlffHeap as Heap;
|
||||||
use smoltcp::iface::{Config, Interface, SocketHandle, SocketSet, SocketStorage};
|
|
||||||
use smoltcp::wire::{HardwareAddress, IpAddress, IpCidr};
|
|
||||||
use stm32h7xx_hal::ethernet;
|
|
||||||
|
|
||||||
const DEFAULT_BLINK_FREQ_MS: u32 = 1000;
|
const DEFAULT_BLINK_FREQ_MS: u32 = 1000;
|
||||||
const PORT: u16 = 7301;
|
const PORT: u16 = 7301;
|
||||||
|
|
||||||
const HEAP_SIZE: usize = 131_072;
|
const HEAP_SIZE: usize = 131_072;
|
||||||
|
|
||||||
const TC_SOURCE_CHANNEL_DEPTH: usize = 16;
|
|
||||||
pub type SharedPool = StaticHeaplessMemoryPool<3>;
|
|
||||||
pub type TcSourceChannel = rtic_sync::channel::Channel<PoolAddr, TC_SOURCE_CHANNEL_DEPTH>;
|
|
||||||
pub type TcSourceTx = rtic_sync::channel::Sender<'static, PoolAddr, TC_SOURCE_CHANNEL_DEPTH>;
|
|
||||||
pub type TcSourceRx = rtic_sync::channel::Receiver<'static, PoolAddr, TC_SOURCE_CHANNEL_DEPTH>;
|
|
||||||
|
|
||||||
#[global_allocator]
|
#[global_allocator]
|
||||||
static HEAP: Heap = Heap::empty();
|
static HEAP: Heap = Heap::empty();
|
||||||
|
|
||||||
systick_monotonic!(Mono, 1000);
|
|
||||||
|
|
||||||
// We place the memory pool buffers inside the larger AXISRAM.
|
|
||||||
pub const SUBPOOL_SMALL_NUM_BLOCKS: u16 = 32;
|
|
||||||
pub const SUBPOOL_SMALL_BLOCK_SIZE: usize = 32;
|
|
||||||
pub const SUBPOOL_MEDIUM_NUM_BLOCKS: u16 = 16;
|
|
||||||
pub const SUBPOOL_MEDIUM_BLOCK_SIZE: usize = 128;
|
|
||||||
pub const SUBPOOL_LARGE_NUM_BLOCKS: u16 = 8;
|
|
||||||
pub const SUBPOOL_LARGE_BLOCK_SIZE: usize = 2048;
|
|
||||||
|
|
||||||
// This data will be held by Net through a mutable reference
|
|
||||||
pub struct NetStorageStatic<'a> {
|
|
||||||
socket_storage: [SocketStorage<'a>; 8],
|
|
||||||
}
|
|
||||||
// MaybeUninit allows us write code that is correct even if STORE is not
|
|
||||||
// initialised by the runtime
|
|
||||||
static mut STORE: MaybeUninit<NetStorageStatic> = MaybeUninit::uninit();
|
|
||||||
|
|
||||||
static mut UDP_RX_META: [udp::PacketMetadata; 12] = [udp::PacketMetadata::EMPTY; 12];
|
|
||||||
static mut UDP_RX: [u8; 2048] = [0; 2048];
|
|
||||||
static mut UDP_TX_META: [udp::PacketMetadata; 12] = [udp::PacketMetadata::EMPTY; 12];
|
|
||||||
static mut UDP_TX: [u8; 2048] = [0; 2048];
|
|
||||||
|
|
||||||
/// Locally administered MAC address
|
/// Locally administered MAC address
|
||||||
const MAC_ADDRESS: [u8; 6] = [0x02, 0x00, 0x11, 0x22, 0x33, 0x44];
|
const MAC_ADDRESS: [u8; 6] = [0x02, 0x00, 0x11, 0x22, 0x33, 0x44];
|
||||||
|
|
||||||
pub struct Net {
|
const TC_QUEUE_DEPTH: usize = 32;
|
||||||
iface: Interface,
|
const TM_QUEUE_DEPTH: usize = 32;
|
||||||
ethdev: ethernet::EthernetDMA<4, 4>,
|
|
||||||
dhcp_handle: SocketHandle,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Net {
|
#[app(device = embassy_stm32, peripherals = false)]
|
||||||
pub fn new(
|
|
||||||
sockets: &mut SocketSet<'static>,
|
|
||||||
mut ethdev: ethernet::EthernetDMA<4, 4>,
|
|
||||||
ethernet_addr: HardwareAddress,
|
|
||||||
) -> Self {
|
|
||||||
let config = Config::new(ethernet_addr);
|
|
||||||
let mut iface = Interface::new(
|
|
||||||
config,
|
|
||||||
&mut ethdev,
|
|
||||||
smoltcp::time::Instant::from_millis(Mono::now().duration_since_epoch().to_millis()),
|
|
||||||
);
|
|
||||||
// Create sockets
|
|
||||||
let dhcp_socket = dhcpv4::Socket::new();
|
|
||||||
iface.update_ip_addrs(|addrs| {
|
|
||||||
let _ = addrs.push(IpCidr::new(IpAddress::v4(192, 168, 1, 99), 0));
|
|
||||||
});
|
|
||||||
|
|
||||||
let dhcp_handle = sockets.add(dhcp_socket);
|
|
||||||
Net {
|
|
||||||
iface,
|
|
||||||
ethdev,
|
|
||||||
dhcp_handle,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Polls on the ethernet interface. You should refer to the smoltcp
|
|
||||||
/// documentation for poll() to understand how to call poll efficiently
|
|
||||||
pub fn poll<'a>(&mut self, sockets: &'a mut SocketSet) -> bool {
|
|
||||||
let uptime = Mono::now().duration_since_epoch();
|
|
||||||
let timestamp = smoltcp::time::Instant::from_millis(uptime.to_millis());
|
|
||||||
|
|
||||||
self.iface.poll(timestamp, &mut self.ethdev, sockets)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn poll_dhcp<'a>(&mut self, sockets: &'a mut SocketSet) -> Option<dhcpv4::Event<'a>> {
|
|
||||||
let opt_event = sockets.get_mut::<dhcpv4::Socket>(self.dhcp_handle).poll();
|
|
||||||
if let Some(event) = &opt_event {
|
|
||||||
match event {
|
|
||||||
dhcpv4::Event::Deconfigured => {
|
|
||||||
defmt::info!("DHCP lost configuration");
|
|
||||||
self.iface.update_ip_addrs(|addrs| addrs.clear());
|
|
||||||
self.iface.routes_mut().remove_default_ipv4_route();
|
|
||||||
}
|
|
||||||
dhcpv4::Event::Configured(config) => {
|
|
||||||
defmt::info!("DHCP configuration acquired");
|
|
||||||
defmt::info!("IP address: {}", config.address);
|
|
||||||
self.iface.update_ip_addrs(|addrs| {
|
|
||||||
addrs.clear();
|
|
||||||
addrs.push(IpCidr::Ipv4(config.address)).unwrap();
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Some(router) = config.router {
|
|
||||||
defmt::debug!("Default gateway: {}", router);
|
|
||||||
self.iface
|
|
||||||
.routes_mut()
|
|
||||||
.add_default_ipv4_route(router)
|
|
||||||
.unwrap();
|
|
||||||
} else {
|
|
||||||
defmt::debug!("Default gateway: None");
|
|
||||||
self.iface.routes_mut().remove_default_ipv4_route();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
opt_event
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct UdpNet {
|
|
||||||
udp_handle: SocketHandle,
|
|
||||||
last_client: Option<UdpMetadata>,
|
|
||||||
tc_source_tx: TcSourceTx,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl UdpNet {
|
|
||||||
pub fn new<'sockets>(sockets: &mut SocketSet<'sockets>, tc_source_tx: TcSourceTx) -> Self {
|
|
||||||
// SAFETY: The RX and TX buffers are passed here and not used anywhere else.
|
|
||||||
let udp_rx_buffer =
|
|
||||||
smoltcp::socket::udp::PacketBuffer::new(unsafe { &mut UDP_RX_META[..] }, unsafe {
|
|
||||||
&mut UDP_RX[..]
|
|
||||||
});
|
|
||||||
let udp_tx_buffer =
|
|
||||||
smoltcp::socket::udp::PacketBuffer::new(unsafe { &mut UDP_TX_META[..] }, unsafe {
|
|
||||||
&mut UDP_TX[..]
|
|
||||||
});
|
|
||||||
let udp_socket = smoltcp::socket::udp::Socket::new(udp_rx_buffer, udp_tx_buffer);
|
|
||||||
|
|
||||||
let udp_handle = sockets.add(udp_socket);
|
|
||||||
Self {
|
|
||||||
udp_handle,
|
|
||||||
last_client: None,
|
|
||||||
tc_source_tx,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn poll<'sockets>(
|
|
||||||
&mut self,
|
|
||||||
sockets: &'sockets mut SocketSet,
|
|
||||||
shared_pool: &mut SharedPool,
|
|
||||||
) {
|
|
||||||
let socket = sockets.get_mut::<udp::Socket>(self.udp_handle);
|
|
||||||
if !socket.is_open() {
|
|
||||||
if let Err(e) = socket.bind(PORT) {
|
|
||||||
defmt::warn!("binding UDP socket failed: {}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
loop {
|
|
||||||
match socket.recv() {
|
|
||||||
Ok((data, client)) => {
|
|
||||||
match shared_pool.add(data) {
|
|
||||||
Ok(store_addr) => {
|
|
||||||
if let Err(e) = self.tc_source_tx.try_send(store_addr) {
|
|
||||||
defmt::warn!("TC source channel is full: {}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
defmt::warn!("could not add UDP packet to shared pool: {}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.last_client = Some(client);
|
|
||||||
// TODO: Implement packet wiretapping.
|
|
||||||
}
|
|
||||||
Err(e) => match e {
|
|
||||||
udp::RecvError::Exhausted => {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
udp::RecvError::Truncated => {
|
|
||||||
defmt::warn!("UDP packet was truncacted");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[app(device = stm32h7xx_hal::stm32, peripherals = true)]
|
|
||||||
mod app {
|
mod app {
|
||||||
use core::ptr::addr_of_mut;
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use rtic_monotonics::fugit::MillisDurationU32;
|
use arbitrary_int::u14;
|
||||||
use satrs::spacepackets::ecss::tc::PusTcReader;
|
use embassy_net::udp::UdpSocket;
|
||||||
use stm32h7xx_hal::ethernet::{EthernetMAC, PHY};
|
use embassy_net::StackResources;
|
||||||
use stm32h7xx_hal::gpio::{Output, Pin};
|
use embassy_stm32::eth;
|
||||||
use stm32h7xx_hal::prelude::*;
|
use embassy_stm32::gpio;
|
||||||
use stm32h7xx_hal::stm32::Interrupt;
|
use embassy_stm32::peripherals;
|
||||||
|
use embassy_stm32::rng;
|
||||||
|
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
|
||||||
|
use embassy_time::Duration;
|
||||||
|
use embassy_time::Timer;
|
||||||
|
use embassy_time::WithTimeout as _;
|
||||||
|
use embedded_models::create_tm_packet;
|
||||||
|
use embedded_models::stm32h7;
|
||||||
|
use embedded_models::tm_size;
|
||||||
|
use embedded_models::TmHeader;
|
||||||
|
use spacepackets::CcsdsPacketCreationError;
|
||||||
|
use spacepackets::CcsdsPacketIdAndPsc;
|
||||||
|
use spacepackets::CcsdsPacketReader;
|
||||||
|
use spacepackets::SpHeader;
|
||||||
|
use static_cell::StaticCell;
|
||||||
|
|
||||||
|
bind_interrupts!(struct Irqs {
|
||||||
|
ETH => eth::InterruptHandler;
|
||||||
|
RNG => rng::InterruptHandler<peripherals::RNG>;
|
||||||
|
});
|
||||||
|
|
||||||
|
type Device = eth::Ethernet<
|
||||||
|
'static,
|
||||||
|
peripherals::ETH,
|
||||||
|
eth::GenericPhy<eth::Sma<'static, peripherals::ETH_SMA>>,
|
||||||
|
>;
|
||||||
|
|
||||||
struct BlinkyLeds {
|
struct BlinkyLeds {
|
||||||
led1: Pin<'B', 7, Output>,
|
led1: gpio::Output<'static>,
|
||||||
led2: Pin<'B', 14, Output>,
|
led2: gpio::Output<'static>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[local]
|
#[local]
|
||||||
struct Local {
|
struct Local {
|
||||||
|
net_runner: embassy_net::Runner<'static, Device>,
|
||||||
|
net_stack: embassy_net::Stack<'static>,
|
||||||
leds: BlinkyLeds,
|
leds: BlinkyLeds,
|
||||||
link_led: Pin<'B', 0, Output>,
|
link_led: gpio::Output<'static>,
|
||||||
net: Net,
|
tc_rx: embassy_sync::channel::Receiver<
|
||||||
udp: UdpNet,
|
'static,
|
||||||
tc_source_rx: TcSourceRx,
|
NoopRawMutex,
|
||||||
phy: ethernet::phy::LAN8742A<EthernetMAC>,
|
alloc::vec::Vec<u8>,
|
||||||
|
TC_QUEUE_DEPTH,
|
||||||
|
>,
|
||||||
|
tc_tx: embassy_sync::channel::Sender<
|
||||||
|
'static,
|
||||||
|
NoopRawMutex,
|
||||||
|
alloc::vec::Vec<u8>,
|
||||||
|
TC_QUEUE_DEPTH,
|
||||||
|
>,
|
||||||
|
tm_rx: embassy_sync::channel::Receiver<
|
||||||
|
'static,
|
||||||
|
NoopRawMutex,
|
||||||
|
alloc::vec::Vec<u8>,
|
||||||
|
TM_QUEUE_DEPTH,
|
||||||
|
>,
|
||||||
|
tm_tx: embassy_sync::channel::Sender<
|
||||||
|
'static,
|
||||||
|
NoopRawMutex,
|
||||||
|
alloc::vec::Vec<u8>,
|
||||||
|
TM_QUEUE_DEPTH,
|
||||||
|
>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[shared]
|
#[shared]
|
||||||
struct Shared {
|
struct Shared {
|
||||||
blink_freq: MillisDurationU32,
|
sequence_count: u14,
|
||||||
eth_link_up: bool,
|
blink_freq: embassy_time::Duration,
|
||||||
sockets: SocketSet<'static>,
|
|
||||||
shared_pool: SharedPool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[init]
|
#[init]
|
||||||
fn init(mut cx: init::Context) -> (Shared, Local) {
|
fn init(_cx: init::Context) -> (Shared, Local) {
|
||||||
defmt::println!("Starting sat-rs demo application for the STM32H743ZIT");
|
defmt::println!("Starting sat-rs demo application for the STM32H743ZIT");
|
||||||
|
|
||||||
let pwr = cx.device.PWR.constrain();
|
let mut config = embassy_stm32::Config::default();
|
||||||
let pwrcfg = pwr.freeze();
|
{
|
||||||
|
use embassy_stm32::rcc::*;
|
||||||
|
config.rcc.hsi = Some(HSIPrescaler::Div1);
|
||||||
|
config.rcc.csi = true;
|
||||||
|
config.rcc.hsi48 = Some(Default::default()); // needed for RNG
|
||||||
|
config.rcc.pll1 = Some(Pll {
|
||||||
|
source: PllSource::Hsi,
|
||||||
|
prediv: PllPreDiv::Div4,
|
||||||
|
mul: PllMul::Mul50,
|
||||||
|
fracn: None,
|
||||||
|
divp: Some(PllDiv::Div2),
|
||||||
|
divq: None,
|
||||||
|
divr: None,
|
||||||
|
});
|
||||||
|
config.rcc.sys = Sysclk::Pll1P; // 400 Mhz
|
||||||
|
config.rcc.ahb_pre = AHBPrescaler::Div2; // 200 Mhz
|
||||||
|
config.rcc.apb1_pre = APBPrescaler::Div2; // 100 Mhz
|
||||||
|
config.rcc.apb2_pre = APBPrescaler::Div2; // 100 Mhz
|
||||||
|
config.rcc.apb3_pre = APBPrescaler::Div2; // 100 Mhz
|
||||||
|
config.rcc.apb4_pre = APBPrescaler::Div2; // 100 Mhz
|
||||||
|
config.rcc.voltage_scale = VoltageScale::Scale1;
|
||||||
|
}
|
||||||
|
let periphs = embassy_stm32::init(config);
|
||||||
|
|
||||||
let rcc = cx.device.RCC.constrain();
|
let link_led = gpio::Output::new(periphs.PB0, gpio::Level::Low, gpio::Speed::Medium);
|
||||||
// Try to keep the clock configuration similar to one used in STM examples:
|
let mut led1 = gpio::Output::new(periphs.PB7, gpio::Level::Low, gpio::Speed::Medium);
|
||||||
// https://github.com/STMicroelectronics/STM32CubeH7/blob/master/Projects/NUCLEO-H743ZI/Examples/GPIO/GPIO_EXTI/Src/main.c
|
let mut led2 = gpio::Output::new(periphs.PB14, gpio::Level::Low, gpio::Speed::Medium);
|
||||||
let ccdr = rcc
|
|
||||||
.sys_ck(400.MHz())
|
|
||||||
.hclk(200.MHz())
|
|
||||||
.use_hse(8.MHz())
|
|
||||||
.bypass_hse()
|
|
||||||
.pclk1(100.MHz())
|
|
||||||
.pclk2(100.MHz())
|
|
||||||
.pclk3(100.MHz())
|
|
||||||
.pclk4(100.MHz())
|
|
||||||
.freeze(pwrcfg, &cx.device.SYSCFG);
|
|
||||||
|
|
||||||
// Initialize the systick interrupt & obtain the token to prove that we did
|
|
||||||
Mono::start(cx.core.SYST, ccdr.clocks.sys_ck().to_Hz());
|
|
||||||
|
|
||||||
// Those are used in the smoltcp of the stm32h7xx-hal , I am not fully sure what they are
|
|
||||||
// good for.
|
|
||||||
cx.core.SCB.enable_icache();
|
|
||||||
cx.core.DWT.enable_cycle_counter();
|
|
||||||
|
|
||||||
let gpioa = cx.device.GPIOA.split(ccdr.peripheral.GPIOA);
|
|
||||||
let gpiob = cx.device.GPIOB.split(ccdr.peripheral.GPIOB);
|
|
||||||
let gpioc = cx.device.GPIOC.split(ccdr.peripheral.GPIOC);
|
|
||||||
let gpiog = cx.device.GPIOG.split(ccdr.peripheral.GPIOG);
|
|
||||||
|
|
||||||
let link_led = gpiob.pb0.into_push_pull_output();
|
|
||||||
let mut led1 = gpiob.pb7.into_push_pull_output();
|
|
||||||
let mut led2 = gpiob.pb14.into_push_pull_output();
|
|
||||||
|
|
||||||
// Criss-cross pattern looks cooler.
|
// Criss-cross pattern looks cooler.
|
||||||
led1.set_high();
|
led1.set_high();
|
||||||
led2.set_low();
|
led2.set_low();
|
||||||
let leds = BlinkyLeds { led1, led2 };
|
let leds = BlinkyLeds { led1, led2 };
|
||||||
|
|
||||||
let rmii_ref_clk = gpioa.pa1.into_alternate::<11>();
|
static PACKETS: StaticCell<eth::PacketQueue<4, 4>> = StaticCell::new();
|
||||||
let rmii_mdio = gpioa.pa2.into_alternate::<11>();
|
// warning: Not all STM32H7 devices have the exact same pins here
|
||||||
let rmii_mdc = gpioc.pc1.into_alternate::<11>();
|
// for STM32H747XIH, replace p.PB13 for PG12
|
||||||
let rmii_crs_dv = gpioa.pa7.into_alternate::<11>();
|
let device = eth::Ethernet::new(
|
||||||
let rmii_rxd0 = gpioc.pc4.into_alternate::<11>();
|
PACKETS.init(eth::PacketQueue::<4, 4>::new()),
|
||||||
let rmii_rxd1 = gpioc.pc5.into_alternate::<11>();
|
periphs.ETH,
|
||||||
let rmii_tx_en = gpiog.pg11.into_alternate::<11>();
|
Irqs,
|
||||||
let rmii_txd0 = gpiog.pg13.into_alternate::<11>();
|
periphs.PA1, // ref_clk
|
||||||
let rmii_txd1 = gpiob.pb13.into_alternate::<11>();
|
periphs.PA7, // CRS_DV: Carrier Sense
|
||||||
|
periphs.PC4, // RX_D0: Received Bit 0
|
||||||
let mac_addr = smoltcp::wire::EthernetAddress::from_bytes(&MAC_ADDRESS);
|
periphs.PC5, // RX_D1: Received Bit 1
|
||||||
|
periphs.PG13, // TX_D0: Transmit Bit 0
|
||||||
/// Ethernet descriptor rings are a global singleton
|
periphs.PB13, // TX_D1: Transmit Bit 1
|
||||||
#[link_section = ".sram3.eth"]
|
periphs.PG11, // TX_EN: Transmit Enable
|
||||||
static mut DES_RING: MaybeUninit<ethernet::DesRing<4, 4>> = MaybeUninit::uninit();
|
MAC_ADDRESS,
|
||||||
|
periphs.ETH_SMA,
|
||||||
let (eth_dma, eth_mac) = ethernet::new(
|
periphs.PA2, // mdio
|
||||||
cx.device.ETHERNET_MAC,
|
periphs.PC1, // mdc
|
||||||
cx.device.ETHERNET_MTL,
|
|
||||||
cx.device.ETHERNET_DMA,
|
|
||||||
(
|
|
||||||
rmii_ref_clk,
|
|
||||||
rmii_mdio,
|
|
||||||
rmii_mdc,
|
|
||||||
rmii_crs_dv,
|
|
||||||
rmii_rxd0,
|
|
||||||
rmii_rxd1,
|
|
||||||
rmii_tx_en,
|
|
||||||
rmii_txd0,
|
|
||||||
rmii_txd1,
|
|
||||||
),
|
|
||||||
// SAFETY: We do not move the returned DMA struct across thread boundaries, so this
|
|
||||||
// should be safe according to the docs.
|
|
||||||
unsafe { DES_RING.assume_init_mut() },
|
|
||||||
mac_addr,
|
|
||||||
ccdr.peripheral.ETH1MAC,
|
|
||||||
&ccdr.clocks,
|
|
||||||
);
|
|
||||||
// Initialise ethernet PHY...
|
|
||||||
let mut lan8742a = ethernet::phy::LAN8742A::new(eth_mac.set_phy_addr(0));
|
|
||||||
lan8742a.phy_reset();
|
|
||||||
lan8742a.phy_init();
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
ethernet::enable_interrupt();
|
|
||||||
cx.core.NVIC.set_priority(Interrupt::ETH, 196); // Mid prio
|
|
||||||
cortex_m::peripheral::NVIC::unmask(Interrupt::ETH);
|
|
||||||
}
|
|
||||||
|
|
||||||
// unsafe: mutable reference to static storage, we only do this once
|
|
||||||
let store = unsafe {
|
|
||||||
let store_ptr = STORE.as_mut_ptr();
|
|
||||||
|
|
||||||
// Initialise the socket_storage field. Using `write` instead of
|
|
||||||
// assignment via `=` to not call `drop` on the old, uninitialised
|
|
||||||
// value
|
|
||||||
addr_of_mut!((*store_ptr).socket_storage).write([SocketStorage::EMPTY; 8]);
|
|
||||||
|
|
||||||
// Now that all fields are initialised we can safely use
|
|
||||||
// assume_init_mut to return a mutable reference to STORE
|
|
||||||
STORE.assume_init_mut()
|
|
||||||
};
|
|
||||||
|
|
||||||
let (tc_source_tx, tc_source_rx) =
|
|
||||||
rtic_sync::make_channel!(PoolAddr, TC_SOURCE_CHANNEL_DEPTH);
|
|
||||||
|
|
||||||
let mut sockets = SocketSet::new(&mut store.socket_storage[..]);
|
|
||||||
let net = Net::new(&mut sockets, eth_dma, mac_addr.into());
|
|
||||||
let udp = UdpNet::new(&mut sockets, tc_source_tx);
|
|
||||||
|
|
||||||
let mut shared_pool: SharedPool = StaticHeaplessMemoryPool::new(true);
|
|
||||||
static_subpool!(
|
|
||||||
SUBPOOL_SMALL,
|
|
||||||
SUBPOOL_SMALL_SIZES,
|
|
||||||
SUBPOOL_SMALL_NUM_BLOCKS as usize,
|
|
||||||
SUBPOOL_SMALL_BLOCK_SIZE,
|
|
||||||
link_section = ".axisram"
|
|
||||||
);
|
|
||||||
static_subpool!(
|
|
||||||
SUBPOOL_MEDIUM,
|
|
||||||
SUBPOOL_MEDIUM_SIZES,
|
|
||||||
SUBPOOL_MEDIUM_NUM_BLOCKS as usize,
|
|
||||||
SUBPOOL_MEDIUM_BLOCK_SIZE,
|
|
||||||
link_section = ".axisram"
|
|
||||||
);
|
|
||||||
static_subpool!(
|
|
||||||
SUBPOOL_LARGE,
|
|
||||||
SUBPOOL_LARGE_SIZES,
|
|
||||||
SUBPOOL_LARGE_NUM_BLOCKS as usize,
|
|
||||||
SUBPOOL_LARGE_BLOCK_SIZE,
|
|
||||||
link_section = ".axisram"
|
|
||||||
);
|
);
|
||||||
|
|
||||||
shared_pool
|
let config = embassy_net::Config::dhcpv4(embassy_net::DhcpConfig::default());
|
||||||
.grow(
|
|
||||||
SUBPOOL_SMALL.get_mut().unwrap(),
|
// Generate random seed.
|
||||||
SUBPOOL_SMALL_SIZES.get_mut().unwrap(),
|
let mut rng = rng::Rng::new(periphs.RNG, Irqs);
|
||||||
SUBPOOL_SMALL_NUM_BLOCKS,
|
let mut seed = [0; 8];
|
||||||
true,
|
rng.fill_bytes(&mut seed);
|
||||||
)
|
let seed = u64::from_le_bytes(seed);
|
||||||
.expect("growing heapless memory pool failed");
|
|
||||||
shared_pool
|
// Init network stack
|
||||||
.grow(
|
static RESOURCES: StaticCell<StackResources<3>> = StaticCell::new();
|
||||||
SUBPOOL_MEDIUM.get_mut().unwrap(),
|
let (stack, runner) =
|
||||||
SUBPOOL_MEDIUM_SIZES.get_mut().unwrap(),
|
embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed);
|
||||||
SUBPOOL_MEDIUM_NUM_BLOCKS,
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
.expect("growing heapless memory pool failed");
|
|
||||||
shared_pool
|
|
||||||
.grow(
|
|
||||||
SUBPOOL_LARGE.get_mut().unwrap(),
|
|
||||||
SUBPOOL_LARGE_SIZES.get_mut().unwrap(),
|
|
||||||
SUBPOOL_LARGE_NUM_BLOCKS,
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
.expect("growing heapless memory pool failed");
|
|
||||||
|
|
||||||
// Set up global allocator. Use AXISRAM for the heap.
|
// Set up global allocator. Use AXISRAM for the heap.
|
||||||
#[link_section = ".axisram"]
|
#[link_section = ".axisram"]
|
||||||
static mut HEAP_MEM: [MaybeUninit<u8>; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE];
|
static mut HEAP_MEM: [MaybeUninit<u8>; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE];
|
||||||
unsafe { HEAP.init(&raw mut HEAP_MEM as usize, HEAP_SIZE) }
|
unsafe { HEAP.init(&raw mut HEAP_MEM as usize, HEAP_SIZE) }
|
||||||
|
|
||||||
eth_link_check::spawn().expect("eth link check failed");
|
static TC_CHANNEL: static_cell::ConstStaticCell<
|
||||||
|
embassy_sync::channel::Channel<NoopRawMutex, alloc::vec::Vec<u8>, TC_QUEUE_DEPTH>,
|
||||||
|
> = static_cell::ConstStaticCell::new(embassy_sync::channel::Channel::new());
|
||||||
|
let tc_channel = TC_CHANNEL.take();
|
||||||
|
let tc_sender = tc_channel.sender();
|
||||||
|
let tc_receiver = tc_channel.receiver();
|
||||||
|
|
||||||
|
static TM_CHANNEL: static_cell::ConstStaticCell<
|
||||||
|
embassy_sync::channel::Channel<NoopRawMutex, alloc::vec::Vec<u8>, TM_QUEUE_DEPTH>,
|
||||||
|
> = static_cell::ConstStaticCell::new(embassy_sync::channel::Channel::new());
|
||||||
|
let tm_channel = TM_CHANNEL.take();
|
||||||
|
let tm_sender = tm_channel.sender();
|
||||||
|
let tm_receiver = tm_channel.receiver();
|
||||||
|
|
||||||
|
net_lib_task::spawn().expect("spawning net library task failed");
|
||||||
|
net_app_task::spawn().expect("spawning net application task failed");
|
||||||
blinky::spawn().expect("spawning blink task failed");
|
blinky::spawn().expect("spawning blink task failed");
|
||||||
udp_task::spawn().expect("spawning UDP task failed");
|
tc_handler::spawn().expect("spawning TC handler task failed");
|
||||||
tc_source_task::spawn().expect("spawning TC source task failed");
|
|
||||||
|
|
||||||
(
|
(
|
||||||
Shared {
|
Shared {
|
||||||
blink_freq: MillisDurationU32::from_ticks(DEFAULT_BLINK_FREQ_MS),
|
blink_freq: Duration::from_millis(DEFAULT_BLINK_FREQ_MS as u64),
|
||||||
eth_link_up: false,
|
sequence_count: u14::new(0),
|
||||||
sockets,
|
|
||||||
shared_pool,
|
|
||||||
},
|
},
|
||||||
Local {
|
Local {
|
||||||
link_led,
|
link_led,
|
||||||
leds,
|
leds,
|
||||||
net,
|
net_runner: runner,
|
||||||
udp,
|
net_stack: stack,
|
||||||
tc_source_rx,
|
tc_tx: tc_sender,
|
||||||
phy: lan8742a,
|
tc_rx: tc_receiver,
|
||||||
|
tm_tx: tm_sender,
|
||||||
|
tm_rx: tm_receiver,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -430,94 +223,159 @@ mod app {
|
|||||||
leds.led1.toggle();
|
leds.led1.toggle();
|
||||||
leds.led2.toggle();
|
leds.led2.toggle();
|
||||||
let current_blink_freq = cx.shared.blink_freq.lock(|current| *current);
|
let current_blink_freq = cx.shared.blink_freq.lock(|current| *current);
|
||||||
Mono::delay(current_blink_freq).await;
|
Timer::after_millis(current_blink_freq.as_millis()).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This task checks for the network link.
|
#[task(local=[net_runner])]
|
||||||
#[task(local=[link_led, phy], shared=[eth_link_up])]
|
async fn net_lib_task(cx: net_lib_task::Context) {
|
||||||
async fn eth_link_check(mut cx: eth_link_check::Context) {
|
cx.local.net_runner.run().await;
|
||||||
let phy = cx.local.phy;
|
}
|
||||||
let link_led = cx.local.link_led;
|
|
||||||
|
#[task(local = [net_stack, link_led, tc_tx, tm_rx])]
|
||||||
|
async fn net_app_task(cx: net_app_task::Context) {
|
||||||
|
pub const MTU: usize = 1500;
|
||||||
|
|
||||||
|
// Ensure those are in the data section by making them static.
|
||||||
|
static RX_UDP_META: static_cell::ConstStaticCell<[embassy_net::udp::PacketMetadata; 8]> =
|
||||||
|
static_cell::ConstStaticCell::new([embassy_net::udp::PacketMetadata::EMPTY; 8]);
|
||||||
|
static TX_UDP_META: static_cell::ConstStaticCell<[embassy_net::udp::PacketMetadata; 8]> =
|
||||||
|
static_cell::ConstStaticCell::new([embassy_net::udp::PacketMetadata::EMPTY; 8]);
|
||||||
|
static TX_UDP_BUFS: static_cell::ConstStaticCell<[u8; MTU]> =
|
||||||
|
static_cell::ConstStaticCell::new([0; MTU]);
|
||||||
|
static RX_UDP_BUFS: static_cell::ConstStaticCell<[u8; MTU]> =
|
||||||
|
static_cell::ConstStaticCell::new([0; MTU]);
|
||||||
|
|
||||||
|
let rx_udp_meta = RX_UDP_META.take();
|
||||||
|
let rx_udp_bufs = RX_UDP_BUFS.take();
|
||||||
|
let tx_udp_meta = TX_UDP_META.take();
|
||||||
|
let tx_udp_bufs = TX_UDP_BUFS.take();
|
||||||
|
|
||||||
|
let mut rx_buffer = [0; MTU];
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let link_was_up = cx.shared.eth_link_up.lock(|link_up| *link_up);
|
cx.local.net_stack.wait_link_up().await;
|
||||||
if phy.poll_link() {
|
cx.local.link_led.set_high();
|
||||||
if !link_was_up {
|
defmt::info!("Network link is up");
|
||||||
link_led.set_high();
|
|
||||||
cx.shared.eth_link_up.lock(|link_up| *link_up = true);
|
// Ensure DHCP configuration is up before trying connect
|
||||||
defmt::info!("Ethernet link up");
|
cx.local.net_stack.wait_config_up().await;
|
||||||
|
|
||||||
|
let config = cx.local.net_stack.config_v4();
|
||||||
|
defmt::info!("Network task initialized, config: {}", config);
|
||||||
|
|
||||||
|
let mut udp = UdpSocket::new(
|
||||||
|
cx.local.net_stack.clone(),
|
||||||
|
rx_udp_meta,
|
||||||
|
rx_udp_bufs,
|
||||||
|
tx_udp_meta,
|
||||||
|
tx_udp_bufs,
|
||||||
|
);
|
||||||
|
defmt::info!("UDP socket bound to port {}", PORT);
|
||||||
|
udp.bind(PORT).expect("failed to bind UDP socket");
|
||||||
|
let mut remote_endpoint = None;
|
||||||
|
loop {
|
||||||
|
if !cx.local.net_stack.is_link_up() {
|
||||||
|
defmt::warn!("Network link is down");
|
||||||
|
cx.local.link_led.set_low();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
} else if link_was_up {
|
match udp
|
||||||
link_led.set_low();
|
.recv_from(&mut rx_buffer)
|
||||||
cx.shared.eth_link_up.lock(|link_up| *link_up = false);
|
.with_timeout(Duration::from_millis(200))
|
||||||
defmt::info!("Ethernet link down");
|
.await
|
||||||
}
|
{
|
||||||
Mono::delay(100.millis()).await;
|
Ok(result) => match result {
|
||||||
}
|
Ok((data, meta)) => {
|
||||||
}
|
remote_endpoint = Some(meta.endpoint);
|
||||||
|
defmt::debug!("UDP RX {}, Meta: {}", data, meta);
|
||||||
#[task(binds=ETH, local=[net], shared=[sockets])]
|
cx.local.tc_tx.send(rx_buffer[0..data].to_vec()).await;
|
||||||
fn eth_isr(mut cx: eth_isr::Context) {
|
|
||||||
// SAFETY: We do not write the register mentioned inside the docs anywhere else.
|
|
||||||
unsafe {
|
|
||||||
ethernet::interrupt_handler();
|
|
||||||
}
|
|
||||||
// Check and process ETH frames and DHCP. UDP is checked in a different task.
|
|
||||||
cx.shared.sockets.lock(|sockets| {
|
|
||||||
cx.local.net.poll(sockets);
|
|
||||||
cx.local.net.poll_dhcp(sockets);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This task routes UDP packets.
|
|
||||||
#[task(local=[udp], shared=[sockets, shared_pool])]
|
|
||||||
async fn udp_task(mut cx: udp_task::Context) {
|
|
||||||
loop {
|
|
||||||
cx.shared.sockets.lock(|sockets| {
|
|
||||||
cx.shared.shared_pool.lock(|pool| {
|
|
||||||
cx.local.udp.poll(sockets, pool);
|
|
||||||
})
|
|
||||||
});
|
|
||||||
Mono::delay(40.millis()).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This task handles all the incoming telecommands.
|
|
||||||
#[task(local=[read_buf: [u8; 1024] = [0; 1024], tc_source_rx], shared=[shared_pool])]
|
|
||||||
async fn tc_source_task(mut cx: tc_source_task::Context) {
|
|
||||||
loop {
|
|
||||||
let recv_result = cx.local.tc_source_rx.recv().await;
|
|
||||||
match recv_result {
|
|
||||||
Ok(pool_addr) => {
|
|
||||||
cx.shared.shared_pool.lock(|pool| {
|
|
||||||
match pool.read(&pool_addr, cx.local.read_buf.as_mut()) {
|
|
||||||
Ok(packet_len) => {
|
|
||||||
defmt::info!("received {} bytes in the TC source task", packet_len);
|
|
||||||
match PusTcReader::new(&cx.local.read_buf[0..packet_len]) {
|
|
||||||
Ok((packet, _tc_len)) => {
|
|
||||||
// TODO: Handle packet here or dispatch to dedicated PUS
|
|
||||||
// handler? Dispatching could simplify some things and make
|
|
||||||
// the software more scalable..
|
|
||||||
defmt::info!("received PUS packet: {}", packet);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
defmt::info!("invalid TC format, not a PUS packet: {}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Err(e) = pool.delete(pool_addr) {
|
|
||||||
defmt::warn!("deleting TC data failed: {}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
defmt::warn!("TC packet read failed: {}", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
Err(e) => {
|
||||||
|
defmt::warn!("udp receive error: {}", e);
|
||||||
|
Timer::after_millis(100).await;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(_e) => (),
|
||||||
}
|
}
|
||||||
Err(e) => {
|
if let Some(endpoint) = remote_endpoint {
|
||||||
defmt::warn!("TC source reception error: {}", e);
|
while let Ok(packet) = cx.local.tm_rx.try_receive() {
|
||||||
|
match udp.send_to(&packet, endpoint).await {
|
||||||
|
Ok(_) => {
|
||||||
|
defmt::debug!("UDP TX: {} bytes to: {}", packet.len(), endpoint)
|
||||||
|
}
|
||||||
|
Err(e) => defmt::warn!("udp send error: {}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[task(local = [tc_rx, tm_tx], shared=[sequence_count, blink_freq])]
|
||||||
|
async fn tc_handler(mut cx: tc_handler::Context) {
|
||||||
|
loop {
|
||||||
|
let tc = cx.local.tc_rx.receive().await;
|
||||||
|
|
||||||
|
match CcsdsPacketReader::new_with_checksum(&tc) {
|
||||||
|
Ok(packet) => {
|
||||||
|
let packet_id = packet.packet_id();
|
||||||
|
let psc = packet.psc();
|
||||||
|
let tc_packet_id = CcsdsPacketIdAndPsc { packet_id, psc };
|
||||||
|
if let Ok(request) =
|
||||||
|
postcard::from_bytes::<stm32h7::Request>(packet.packet_data())
|
||||||
|
{
|
||||||
|
let response = match request {
|
||||||
|
stm32h7::Request::Ping => {
|
||||||
|
stm32h7::Response::Ok
|
||||||
|
}
|
||||||
|
stm32h7::Request::ChangeBlinkFrequency(duration) => {
|
||||||
|
cx.shared.blink_freq.lock(|current| {
|
||||||
|
*current = Duration::from_millis(duration.as_millis() as u64)
|
||||||
|
});
|
||||||
|
stm32h7::Response::Ok
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let sequence_count = cx.shared.sequence_count.lock(|v| {
|
||||||
|
let current = *v;
|
||||||
|
*v = v.wrapping_add(u14::new(1));
|
||||||
|
current
|
||||||
|
});
|
||||||
|
|
||||||
|
// Send Pong/OK response immediately.
|
||||||
|
if let Err(e) =
|
||||||
|
send_tm(tc_packet_id, response, sequence_count, cx.local.tm_tx).await
|
||||||
|
{
|
||||||
|
defmt::warn!("Failed to send TM response: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => defmt::warn!("Failed to parse received TC packet: {}", e,),
|
||||||
|
}
|
||||||
|
defmt::info!("Received from UDP client: {}", tc.as_slice());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn send_tm(
|
||||||
|
tc_packet_id: CcsdsPacketIdAndPsc,
|
||||||
|
response: stm32h7::Response,
|
||||||
|
current_seq_count: u14,
|
||||||
|
sender: &embassy_sync::channel::Sender<
|
||||||
|
'static,
|
||||||
|
NoopRawMutex,
|
||||||
|
alloc::vec::Vec<u8>,
|
||||||
|
TM_QUEUE_DEPTH,
|
||||||
|
>,
|
||||||
|
) -> Result<(), CcsdsPacketCreationError> {
|
||||||
|
let sp_header = SpHeader::new_for_unseg_tc(stm32h7::PUS_APID, current_seq_count, 0);
|
||||||
|
let tm_header = TmHeader {
|
||||||
|
tc_packet_id: Some(tc_packet_id),
|
||||||
|
uptime_millis: embassy_time::Instant::now().as_millis(),
|
||||||
|
};
|
||||||
|
let tm_size = tm_size(&tm_header, &response);
|
||||||
|
let mut packet = alloc::vec![0; tm_size];
|
||||||
|
create_tm_packet(&mut packet, sp_header, tm_header, response)?;
|
||||||
|
sender.send(packet).await;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#![no_std]
|
#![no_std]
|
||||||
#![no_main]
|
#![no_main]
|
||||||
|
|
||||||
use stm32h7_testapp as _; // memory layout + panic handler
|
use satrs_stm32h7_nucleo_rtic as _; // memory layout + panic handler
|
||||||
|
|
||||||
// See https://crates.io/crates/defmt-test/0.3.0 for more documentation (e.g. about the 'state'
|
// See https://crates.io/crates/defmt-test/0.3.0 for more documentation (e.g. about the 'state'
|
||||||
// feature)
|
// feature)
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
/settings.json
|
|
||||||
/.cortex-debug.*
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
// See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
|
|
||||||
// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
|
|
||||||
|
|
||||||
// List of extensions which should be recommended for users of this workspace.
|
|
||||||
"recommendations": [
|
|
||||||
"rust-lang.rust",
|
|
||||||
"probe-rs.probe-rs-debugger"
|
|
||||||
],
|
|
||||||
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
|
|
||||||
"unwantedRecommendations": []
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
{
|
|
||||||
"version": "0.2.0",
|
|
||||||
"configurations": [
|
|
||||||
{
|
|
||||||
"preLaunchTask": "${defaultBuildTask}",
|
|
||||||
"type": "probe-rs-debug",
|
|
||||||
"request": "launch",
|
|
||||||
"name": "probe-rs Debugging ",
|
|
||||||
"flashingConfig": {
|
|
||||||
"flashingEnabled": true
|
|
||||||
},
|
|
||||||
"chip": "STM32H743ZITx",
|
|
||||||
"coreConfigs": [
|
|
||||||
{
|
|
||||||
"programBinary": "${workspaceFolder}/target/thumbv7em-none-eabihf/debug/satrs-stm32h7-nucleo-rtic",
|
|
||||||
"rttEnabled": true,
|
|
||||||
"svdFile": "STM32H743.svd"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
{
|
|
||||||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
|
||||||
// for the documentation about the tasks.json format
|
|
||||||
"version": "2.0.0",
|
|
||||||
"tasks": [
|
|
||||||
{
|
|
||||||
"label": "cargo build",
|
|
||||||
"type": "shell",
|
|
||||||
"command": "~/.cargo/bin/cargo", // note: full path to the cargo
|
|
||||||
"args": [
|
|
||||||
"build"
|
|
||||||
],
|
|
||||||
"group": {
|
|
||||||
"kind": "build",
|
|
||||||
"isDefault": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -11,9 +11,17 @@ test:
|
|||||||
cargo nextest run --all-features
|
cargo nextest run --all-features
|
||||||
cargo test --doc --all-features
|
cargo test --doc --all-features
|
||||||
|
|
||||||
embedded:
|
embedded: embedded-stm32h7 embedded-stm32f3
|
||||||
cargo check -p satrs --target=thumbv7em-none-eabihf --no-default-features
|
cargo check -p satrs --target=thumbv7em-none-eabihf --no-default-features
|
||||||
|
|
||||||
|
[working-directory:"embedded-examples/stm32h7-nucleo-rtic"]
|
||||||
|
embedded-stm32h7:
|
||||||
|
cargo build --target=thumbv7em-none-eabihf --release
|
||||||
|
|
||||||
|
[working-directory:"embedded-examples/stm32f3-disco-rtic"]
|
||||||
|
embedded-stm32f3:
|
||||||
|
cargo build --target=thumbv7em-none-eabihf --release
|
||||||
|
|
||||||
check-fmt:
|
check-fmt:
|
||||||
cargo fmt --all -- --check
|
cargo fmt --all -- --check
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use anyhow::bail;
|
use anyhow::bail;
|
||||||
use arbitrary_int::u11;
|
use arbitrary_int::u11;
|
||||||
use clap::Parser as _;
|
use clap::Parser as _;
|
||||||
use models::{Apid, MessageType, TcHeader, acs::mgm::request::HkRequest};
|
use models::{Apid, MessageType, TcHeader, mgm::request::HkRequest};
|
||||||
use satrs_example::config::{OBSW_SERVER_ADDR, SERVER_PORT};
|
use satrs_example::config::{OBSW_SERVER_ADDR, SERVER_PORT};
|
||||||
use spacepackets::{CcsdsPacketIdAndPsc, SpacePacketHeader};
|
use spacepackets::{CcsdsPacketIdAndPsc, SpacePacketHeader};
|
||||||
use std::{
|
use std::{
|
||||||
@@ -136,7 +136,7 @@ fn main() -> anyhow::Result<()> {
|
|||||||
let request = models::ccsds::CcsdsTcPacketOwned::new_with_request(
|
let request = models::ccsds::CcsdsTcPacketOwned::new_with_request(
|
||||||
SpacePacketHeader::new_from_apid(u11::new(Apid::Acs as u16)),
|
SpacePacketHeader::new_from_apid(u11::new(Apid::Acs as u16)),
|
||||||
TcHeader::new(cmd.target_id(), models::MessageType::Ping),
|
TcHeader::new(cmd.target_id(), models::MessageType::Ping),
|
||||||
models::acs::mgm::request::Request::Ping,
|
models::mgm::request::Request::Ping,
|
||||||
);
|
);
|
||||||
let sent_tc_id = CcsdsPacketIdAndPsc::new_from_ccsds_packet(&request.sp_header);
|
let sent_tc_id = CcsdsPacketIdAndPsc::new_from_ccsds_packet(&request.sp_header);
|
||||||
log::info!(
|
log::info!(
|
||||||
@@ -151,8 +151,8 @@ fn main() -> anyhow::Result<()> {
|
|||||||
let request = models::ccsds::CcsdsTcPacketOwned::new_with_request(
|
let request = models::ccsds::CcsdsTcPacketOwned::new_with_request(
|
||||||
SpacePacketHeader::new_from_apid(u11::new(Apid::Acs as u16)),
|
SpacePacketHeader::new_from_apid(u11::new(Apid::Acs as u16)),
|
||||||
TcHeader::new(target_id, models::MessageType::Hk),
|
TcHeader::new(target_id, models::MessageType::Hk),
|
||||||
models::acs::mgm::request::Request::Hk(HkRequest {
|
models::mgm::request::Request::Hk(HkRequest {
|
||||||
id: models::acs::mgm::request::HkId::Sensor,
|
id: models::mgm::request::HkId::Sensor,
|
||||||
req_type: models::HkRequestType::OneShot,
|
req_type: models::HkRequestType::OneShot,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@@ -174,8 +174,8 @@ fn main() -> anyhow::Result<()> {
|
|||||||
let request = models::ccsds::CcsdsTcPacketOwned::new_with_request(
|
let request = models::ccsds::CcsdsTcPacketOwned::new_with_request(
|
||||||
SpacePacketHeader::new_from_apid(u11::new(Apid::Acs as u16)),
|
SpacePacketHeader::new_from_apid(u11::new(Apid::Acs as u16)),
|
||||||
TcHeader::new(target_id, models::MessageType::Mode),
|
TcHeader::new(target_id, models::MessageType::Mode),
|
||||||
models::acs::mgm::request::Request::Mode(
|
models::mgm::request::Request::Mode(
|
||||||
models::acs::mgm::request::ModeRequest::SetMode(dev_mode),
|
models::mgm::request::ModeRequest::SetMode(dev_mode),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
let sent_tc_id = CcsdsPacketIdAndPsc::new_from_ccsds_packet(&request.sp_header);
|
let sent_tc_id = CcsdsPacketIdAndPsc::new_from_ccsds_packet(&request.sp_header);
|
||||||
@@ -193,7 +193,7 @@ fn main() -> anyhow::Result<()> {
|
|||||||
let request = models::ccsds::CcsdsTcPacketOwned::new_with_request(
|
let request = models::ccsds::CcsdsTcPacketOwned::new_with_request(
|
||||||
SpacePacketHeader::new_from_apid(u11::new(Apid::Acs as u16)),
|
SpacePacketHeader::new_from_apid(u11::new(Apid::Acs as u16)),
|
||||||
TcHeader::new(cmd.target_id(), models::MessageType::Ping),
|
TcHeader::new(cmd.target_id(), models::MessageType::Ping),
|
||||||
models::acs::mgm::request::Request::Ping,
|
models::mgm::request::Request::Ping,
|
||||||
);
|
);
|
||||||
let sent_tc_id = CcsdsPacketIdAndPsc::new_from_ccsds_packet(&request.sp_header);
|
let sent_tc_id = CcsdsPacketIdAndPsc::new_from_ccsds_packet(&request.sp_header);
|
||||||
log::info!(
|
log::info!(
|
||||||
@@ -207,23 +207,21 @@ fn main() -> anyhow::Result<()> {
|
|||||||
if let Some(mode) = mgm_assembly_args.mode {
|
if let Some(mode) = mgm_assembly_args.mode {
|
||||||
let assembly_mode = match mode {
|
let assembly_mode = match mode {
|
||||||
AssemblyModeSelect::NoModeKeeping => {
|
AssemblyModeSelect::NoModeKeeping => {
|
||||||
models::acs::mgm_assembly::Mode::NoModeKeeping
|
models::mgm_assembly::AssemblyMode::NoModeKeeping
|
||||||
}
|
}
|
||||||
AssemblyModeSelect::Off => {
|
AssemblyModeSelect::Off => {
|
||||||
models::acs::mgm_assembly::Mode::Device(models::DeviceMode::Off)
|
models::mgm_assembly::AssemblyMode::Device(models::DeviceMode::Off)
|
||||||
}
|
}
|
||||||
AssemblyModeSelect::Normal => {
|
AssemblyModeSelect::Normal => {
|
||||||
models::acs::mgm_assembly::Mode::Device(
|
models::mgm_assembly::AssemblyMode::Device(models::DeviceMode::Normal)
|
||||||
models::DeviceMode::Normal,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let request = models::ccsds::CcsdsTcPacketOwned::new_with_request(
|
let request = models::ccsds::CcsdsTcPacketOwned::new_with_request(
|
||||||
SpacePacketHeader::new_from_apid(u11::new(Apid::Acs as u16)),
|
SpacePacketHeader::new_from_apid(u11::new(Apid::Acs as u16)),
|
||||||
TcHeader::new(target_id, models::MessageType::Mode),
|
TcHeader::new(target_id, models::MessageType::Mode),
|
||||||
models::acs::mgm_assembly::request::Request::Mode(
|
models::mgm_assembly::request::Request::Mode(
|
||||||
models::acs::mgm_assembly::request::ModeRequest::SetMode(assembly_mode),
|
models::mgm_assembly::request::ModeRequest::SetMode(assembly_mode),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
let sent_tc_id = CcsdsPacketIdAndPsc::new_from_ccsds_packet(&request.sp_header);
|
let sent_tc_id = CcsdsPacketIdAndPsc::new_from_ccsds_packet(&request.sp_header);
|
||||||
@@ -306,9 +304,8 @@ fn handle_raw_tm_packet(data: &[u8]) -> anyhow::Result<()> {
|
|||||||
}
|
}
|
||||||
models::ComponentId::AcsSubsystem => todo!(),
|
models::ComponentId::AcsSubsystem => todo!(),
|
||||||
models::ComponentId::AcsMgmAssembly => {
|
models::ComponentId::AcsMgmAssembly => {
|
||||||
let response = postcard::from_bytes::<
|
let response =
|
||||||
models::acs::mgm_assembly::response::Response,
|
postcard::from_bytes::<models::mgm_assembly::response::Response>(remainder);
|
||||||
>(remainder);
|
|
||||||
log::info!(
|
log::info!(
|
||||||
"Received response from MGM Assembly: {:?}",
|
"Received response from MGM Assembly: {:?}",
|
||||||
response.unwrap()
|
response.unwrap()
|
||||||
@@ -316,12 +313,12 @@ fn handle_raw_tm_packet(data: &[u8]) -> anyhow::Result<()> {
|
|||||||
}
|
}
|
||||||
models::ComponentId::AcsMgm0 => {
|
models::ComponentId::AcsMgm0 => {
|
||||||
let response =
|
let response =
|
||||||
postcard::from_bytes::<models::acs::mgm::response::Response>(remainder);
|
postcard::from_bytes::<models::mgm::response::Response>(remainder);
|
||||||
log::info!("Received response from MGM0: {:?}", response.unwrap());
|
log::info!("Received response from MGM0: {:?}", response.unwrap());
|
||||||
}
|
}
|
||||||
models::ComponentId::AcsMgm1 => {
|
models::ComponentId::AcsMgm1 => {
|
||||||
let response =
|
let response =
|
||||||
postcard::from_bytes::<models::acs::mgm::response::Response>(remainder);
|
postcard::from_bytes::<models::mgm::response::Response>(remainder);
|
||||||
log::info!("Received response from MGM1: {:?}", response.unwrap());
|
log::info!("Received response from MGM1: {:?}", response.unwrap());
|
||||||
}
|
}
|
||||||
models::ComponentId::EpsSubsystem => todo!(),
|
models::ComponentId::EpsSubsystem => todo!(),
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
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]),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
pub enum Mode {
|
|
||||||
Off,
|
|
||||||
Normal,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod request {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
|
||||||
pub enum ModeRequest {
|
|
||||||
SetMode(Mode),
|
|
||||||
ReadMode,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod response {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[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]),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
pub mod ctrl;
|
|
||||||
pub mod mgm;
|
|
||||||
pub mod mgm_assembly;
|
|
||||||
pub mod mgt;
|
|
||||||
pub mod subsystem;
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
pub enum Mode {
|
|
||||||
Off,
|
|
||||||
Safe,
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -6,9 +6,10 @@ use spacepackets::{
|
|||||||
time::cds::{CdsTime, MIN_CDS_FIELD_LEN},
|
time::cds::{CdsTime, MIN_CDS_FIELD_LEN},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub mod acs;
|
|
||||||
pub mod ccsds;
|
pub mod ccsds;
|
||||||
pub mod control;
|
pub mod control;
|
||||||
|
pub mod mgm;
|
||||||
|
pub mod mgm_assembly;
|
||||||
pub mod pcdu;
|
pub mod pcdu;
|
||||||
|
|
||||||
#[derive(
|
#[derive(
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ pub mod request {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Copy, Clone, serde::Serialize, serde::Deserialize)]
|
#[derive(Default, Debug, Copy, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct SensorData {
|
pub struct MgmData {
|
||||||
pub valid: bool,
|
pub valid: bool,
|
||||||
pub x: f32,
|
pub x: f32,
|
||||||
pub y: f32,
|
pub y: f32,
|
||||||
@@ -50,11 +50,11 @@ pub struct SensorData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub mod response {
|
pub mod response {
|
||||||
use crate::{DeviceMode, Message, acs::mgm::SensorData};
|
use crate::{DeviceMode, Message, mgm::MgmData};
|
||||||
|
|
||||||
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug)]
|
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug)]
|
||||||
pub enum HkResponse {
|
pub enum HkResponse {
|
||||||
MgmData(SensorData),
|
MgmData(MgmData),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Copy)]
|
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Copy)]
|
||||||
+9
-9
@@ -3,7 +3,7 @@ use core::str::FromStr;
|
|||||||
use crate::DeviceMode;
|
use crate::DeviceMode;
|
||||||
|
|
||||||
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum Mode {
|
pub enum AssemblyMode {
|
||||||
/// The assembly mode ressembles the modes of the devices it controls. It also tries to keep
|
/// 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.
|
/// the children in the correct mode by re-commanding them into the correct mode.
|
||||||
Device(DeviceMode),
|
Device(DeviceMode),
|
||||||
@@ -11,22 +11,22 @@ pub enum Mode {
|
|||||||
NoModeKeeping,
|
NoModeKeeping,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for Mode {
|
impl FromStr for AssemblyMode {
|
||||||
type Err = ();
|
type Err = ();
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
match s.to_lowercase().as_str() {
|
match s.to_lowercase().as_str() {
|
||||||
"off" => Ok(Mode::Device(DeviceMode::Off)),
|
"off" => Ok(AssemblyMode::Device(DeviceMode::Off)),
|
||||||
"on" => Ok(Mode::Device(DeviceMode::On)),
|
"on" => Ok(AssemblyMode::Device(DeviceMode::On)),
|
||||||
"normal" => Ok(Mode::Device(DeviceMode::Normal)),
|
"normal" => Ok(AssemblyMode::Device(DeviceMode::Normal)),
|
||||||
"no_mode_keeping" => Ok(Mode::NoModeKeeping),
|
"no_mode_keeping" => Ok(AssemblyMode::NoModeKeeping),
|
||||||
_ => Err(()),
|
_ => Err(()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod request {
|
pub mod request {
|
||||||
use crate::{HkRequestType, Message, acs::mgm_assembly::Mode};
|
use crate::{HkRequestType, Message, mgm_assembly::AssemblyMode};
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, PartialEq, Eq, Clone, Copy, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum HkId {
|
pub enum HkId {
|
||||||
@@ -35,7 +35,7 @@ pub mod request {
|
|||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum ModeRequest {
|
pub enum ModeRequest {
|
||||||
SetMode(Mode),
|
SetMode(AssemblyMode),
|
||||||
ReadMode,
|
ReadMode,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,7 +77,7 @@ pub mod response {
|
|||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum ModeReport {
|
pub enum ModeReport {
|
||||||
/// Mode of the assembly.
|
/// Mode of the assembly.
|
||||||
Mode(super::Mode),
|
Mode(super::AssemblyMode),
|
||||||
/// Timeout failure setting the children modes.
|
/// Timeout failure setting the children modes.
|
||||||
SetModeTimeout([Option<DeviceMode>; 2]),
|
SetModeTimeout([Option<DeviceMode>; 2]),
|
||||||
/// Children are in wrong mode after commanding.
|
/// Children are in wrong mode after commanding.
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
use models::acs::mgm::SensorData;
|
use models::mgm::MgmData;
|
||||||
use models::acs::mgm::request::ModeRequest;
|
use models::mgm::request::ModeRequest;
|
||||||
use models::acs::mgm::response::ModeResponse;
|
use models::mgm::response::ModeResponse;
|
||||||
use models::pcdu::SwitchId;
|
use models::pcdu::SwitchId;
|
||||||
use models::{ComponentId, DeviceMode, HkRequestType, acs::mgm};
|
use models::{ComponentId, DeviceMode, HkRequestType, mgm};
|
||||||
use satrs::spacepackets::CcsdsPacketIdAndPsc;
|
use satrs::spacepackets::CcsdsPacketIdAndPsc;
|
||||||
use satrs_example::{HkHelperSingleSet, ModeHelper, TimestampHelper, TmtcQueues};
|
use satrs_example::{HkHelperSingleSet, ModeHelper, TimestampHelper, TmtcQueues};
|
||||||
use satrs_minisim::acs::MgmRequestLis3Mdl;
|
use satrs_minisim::acs::MgmRequestLis3Mdl;
|
||||||
@@ -153,7 +153,7 @@ pub struct MgmHandlerLis3Mdl {
|
|||||||
switch_helper: PowerSwitchHelper,
|
switch_helper: PowerSwitchHelper,
|
||||||
tmtc_queues: TmtcQueues,
|
tmtc_queues: TmtcQueues,
|
||||||
pub spi_com: SpiCommunication,
|
pub spi_com: SpiCommunication,
|
||||||
shared_mgm_set: Arc<Mutex<SensorData>>,
|
shared_mgm_set: Arc<Mutex<MgmData>>,
|
||||||
buffers: BufWrapper,
|
buffers: BufWrapper,
|
||||||
stamp_helper: TimestampHelper,
|
stamp_helper: TimestampHelper,
|
||||||
hk_helper: HkHelperSingleSet,
|
hk_helper: HkHelperSingleSet,
|
||||||
@@ -167,7 +167,7 @@ impl MgmHandlerLis3Mdl {
|
|||||||
tmtc_queues: TmtcQueues,
|
tmtc_queues: TmtcQueues,
|
||||||
switch_helper: PowerSwitchHelper,
|
switch_helper: PowerSwitchHelper,
|
||||||
spi_com: SpiCommunication,
|
spi_com: SpiCommunication,
|
||||||
shared_mgm_set: Arc<Mutex<SensorData>>,
|
shared_mgm_set: Arc<Mutex<MgmData>>,
|
||||||
mode_leaf_helper: ModeLeafHelper,
|
mode_leaf_helper: ModeLeafHelper,
|
||||||
mode_timeout: Duration,
|
mode_timeout: Duration,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
@@ -308,7 +308,7 @@ impl MgmHandlerLis3Mdl {
|
|||||||
pub fn handle_hk_request(
|
pub fn handle_hk_request(
|
||||||
&mut self,
|
&mut self,
|
||||||
tc_id: Option<CcsdsPacketIdAndPsc>,
|
tc_id: Option<CcsdsPacketIdAndPsc>,
|
||||||
hk_request: &models::acs::mgm::request::HkRequest,
|
hk_request: &models::mgm::request::HkRequest,
|
||||||
) {
|
) {
|
||||||
match hk_request.req_type {
|
match hk_request.req_type {
|
||||||
HkRequestType::OneShot => {
|
HkRequestType::OneShot => {
|
||||||
@@ -462,8 +462,8 @@ mod tests {
|
|||||||
use arbitrary_int::u11;
|
use arbitrary_int::u11;
|
||||||
use models::{
|
use models::{
|
||||||
Apid, ComponentId, TcHeader,
|
Apid, ComponentId, TcHeader,
|
||||||
acs::mgm::request::HkRequest,
|
|
||||||
ccsds::{CcsdsTcPacketOwned, CcsdsTmPacketOwned},
|
ccsds::{CcsdsTcPacketOwned, CcsdsTmPacketOwned},
|
||||||
|
mgm::request::HkRequest,
|
||||||
pcdu::{SwitchRequest, SwitchState, SwitchStateBinary},
|
pcdu::{SwitchRequest, SwitchState, SwitchStateBinary},
|
||||||
};
|
};
|
||||||
use satrs::{request::GenericMessage, spacepackets::SpacePacketHeader};
|
use satrs::{request::GenericMessage, spacepackets::SpacePacketHeader};
|
||||||
@@ -490,7 +490,7 @@ mod tests {
|
|||||||
|
|
||||||
pub fn create_request_tc(
|
pub fn create_request_tc(
|
||||||
select: MgmSelect,
|
select: MgmSelect,
|
||||||
request: models::acs::mgm::request::Request,
|
request: models::mgm::request::Request,
|
||||||
) -> models::ccsds::CcsdsTcPacketOwned {
|
) -> models::ccsds::CcsdsTcPacketOwned {
|
||||||
models::ccsds::CcsdsTcPacketOwned::new_with_request(
|
models::ccsds::CcsdsTcPacketOwned::new_with_request(
|
||||||
SpacePacketHeader::new_from_apid(u11::new(Apid::Acs as u16)),
|
SpacePacketHeader::new_from_apid(u11::new(Apid::Acs as u16)),
|
||||||
@@ -533,7 +533,7 @@ mod tests {
|
|||||||
SpiCommunication::Test(TestSpiInterface::default()),
|
SpiCommunication::Test(TestSpiInterface::default()),
|
||||||
shared_mgm_set,
|
shared_mgm_set,
|
||||||
mode_leaf_helper,
|
mode_leaf_helper,
|
||||||
Duration::from_millis(100),
|
Duration::from_millis(200),
|
||||||
);
|
);
|
||||||
Self {
|
Self {
|
||||||
assembly_mode_request_tx,
|
assembly_mode_request_tx,
|
||||||
@@ -601,10 +601,9 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(tm_packet.tm_header.sender_id, ComponentId::AcsMgm0);
|
assert_eq!(tm_packet.tm_header.sender_id, ComponentId::AcsMgm0);
|
||||||
|
|
||||||
let response =
|
let response = postcard::from_bytes::<models::mgm::response::Response>(&tm_packet.payload)
|
||||||
postcard::from_bytes::<models::acs::mgm::response::Response>(&tm_packet.payload)
|
.expect("failed to deserialize mode reply");
|
||||||
.expect("failed to deserialize mode reply");
|
matches!(response, models::mgm::response::Response::Ok);
|
||||||
matches!(response, models::acs::mgm::response::Response::Ok);
|
|
||||||
// The device should have been polled once.
|
// The device should have been polled once.
|
||||||
assert_eq!(testbench.test_spi_interface().call_count, 1);
|
assert_eq!(testbench.test_spi_interface().call_count, 1);
|
||||||
let mgm_set = *testbench.handler.shared_mgm_set.lock().unwrap();
|
let mgm_set = *testbench.handler.shared_mgm_set.lock().unwrap();
|
||||||
@@ -690,10 +689,9 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(tm_packet.tm_header.sender_id, ComponentId::AcsMgm0);
|
assert_eq!(tm_packet.tm_header.sender_id, ComponentId::AcsMgm0);
|
||||||
|
|
||||||
let response =
|
let response = postcard::from_bytes::<models::mgm::response::Response>(&tm_packet.payload)
|
||||||
postcard::from_bytes::<models::acs::mgm::response::Response>(&tm_packet.payload)
|
.expect("failed to deserialize mode reply");
|
||||||
.expect("failed to deserialize mode reply");
|
if let models::mgm::response::Response::Hk(mgm::response::HkResponse::MgmData(data)) =
|
||||||
if let models::acs::mgm::response::Response::Hk(mgm::response::HkResponse::MgmData(data)) =
|
|
||||||
response
|
response
|
||||||
{
|
{
|
||||||
assert_eq!(data.valid, false);
|
assert_eq!(data.valid, false);
|
||||||
@@ -742,18 +740,17 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(mode_tm.tm_header.sender_id, ComponentId::AcsMgm0);
|
assert_eq!(mode_tm.tm_header.sender_id, ComponentId::AcsMgm0);
|
||||||
|
|
||||||
let response =
|
let response = postcard::from_bytes::<models::mgm::response::Response>(&mode_tm.payload)
|
||||||
postcard::from_bytes::<models::acs::mgm::response::Response>(&mode_tm.payload)
|
.expect("failed to deserialize mode reply");
|
||||||
.expect("failed to deserialize mode reply");
|
matches!(response, models::mgm::response::Response::Ok);
|
||||||
matches!(response, models::acs::mgm::response::Response::Ok);
|
|
||||||
|
|
||||||
let hk_tm = testbench.tm_rx.try_recv().expect("no hk reply generated");
|
let hk_tm = testbench.tm_rx.try_recv().expect("no hk reply generated");
|
||||||
|
|
||||||
assert_eq!(hk_tm.tm_header.sender_id, ComponentId::AcsMgm0);
|
assert_eq!(hk_tm.tm_header.sender_id, ComponentId::AcsMgm0);
|
||||||
|
|
||||||
let response = postcard::from_bytes::<models::acs::mgm::response::Response>(&hk_tm.payload)
|
let response = postcard::from_bytes::<models::mgm::response::Response>(&hk_tm.payload)
|
||||||
.expect("failed to deserialize mode reply");
|
.expect("failed to deserialize mode reply");
|
||||||
if let models::acs::mgm::response::Response::Hk(mgm::response::HkResponse::MgmData(data)) =
|
if let models::mgm::response::Response::Hk(mgm::response::HkResponse::MgmData(data)) =
|
||||||
response
|
response
|
||||||
{
|
{
|
||||||
// Set is now valid.
|
// Set is now valid.
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use std::{sync::mpsc, time::Duration};
|
|||||||
|
|
||||||
use models::{
|
use models::{
|
||||||
ComponentId, DeviceMode,
|
ComponentId, DeviceMode,
|
||||||
acs::mgm_assembly::{Mode, request, response},
|
mgm_assembly::{AssemblyMode, request, response},
|
||||||
};
|
};
|
||||||
use satrs::spacepackets::CcsdsPacketIdAndPsc;
|
use satrs::spacepackets::CcsdsPacketIdAndPsc;
|
||||||
use satrs_example::{ModeHelper, TmtcQueues};
|
use satrs_example::{ModeHelper, TmtcQueues};
|
||||||
@@ -16,8 +16,8 @@ pub struct ParentQueueHelper {
|
|||||||
|
|
||||||
/// Helper component for communication with a parent component, which is usually as assembly.
|
/// Helper component for communication with a parent component, which is usually as assembly.
|
||||||
pub struct ChildrenQueueHelper {
|
pub struct ChildrenQueueHelper {
|
||||||
pub request_tx_queues: [mpsc::SyncSender<models::acs::mgm::request::ModeRequest>; 2],
|
pub request_tx_queues: [mpsc::SyncSender<models::mgm::request::ModeRequest>; 2],
|
||||||
pub report_rx_queues: [mpsc::Receiver<models::acs::mgm::response::ModeResponse>; 2],
|
pub report_rx_queues: [mpsc::Receiver<models::mgm::response::ModeResponse>; 2],
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
|
||||||
@@ -35,7 +35,7 @@ pub struct MgmInfo {
|
|||||||
|
|
||||||
/// MGM assembly component.
|
/// MGM assembly component.
|
||||||
pub struct Assembly {
|
pub struct Assembly {
|
||||||
mode_helper: ModeHelper<Mode, TransitionState>,
|
mode_helper: ModeHelper<AssemblyMode, TransitionState>,
|
||||||
/// This boolean is used for the distinction between transitions commanded by the parent
|
/// This boolean is used for the distinction between transitions commanded by the parent
|
||||||
/// or by ground, and transitions which were commanded autonomously as part of children
|
/// or by ground, and transitions which were commanded autonomously as part of children
|
||||||
/// mode keeping.
|
/// mode keeping.
|
||||||
@@ -56,7 +56,7 @@ impl Assembly {
|
|||||||
mode_timeout: Duration,
|
mode_timeout: Duration,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
mode_helper: ModeHelper::new(Mode::NoModeKeeping, mode_timeout),
|
mode_helper: ModeHelper::new(AssemblyMode::NoModeKeeping, mode_timeout),
|
||||||
mode_keeping_transition: false,
|
mode_keeping_transition: false,
|
||||||
tmtc_queues,
|
tmtc_queues,
|
||||||
mgm_modes: [MgmInfo::default(); 2],
|
mgm_modes: [MgmInfo::default(); 2],
|
||||||
@@ -80,14 +80,14 @@ impl Assembly {
|
|||||||
match self.tmtc_queues.tc_rx.try_recv() {
|
match self.tmtc_queues.tc_rx.try_recv() {
|
||||||
Ok(packet) => {
|
Ok(packet) => {
|
||||||
let tc_id = CcsdsPacketIdAndPsc::new_from_ccsds_packet(&packet.sp_header);
|
let tc_id = CcsdsPacketIdAndPsc::new_from_ccsds_packet(&packet.sp_header);
|
||||||
match postcard::from_bytes::<models::acs::mgm_assembly::request::Request>(
|
match postcard::from_bytes::<models::mgm_assembly::request::Request>(
|
||||||
&packet.payload,
|
&packet.payload,
|
||||||
) {
|
) {
|
||||||
Ok(request) => match request {
|
Ok(request) => match request {
|
||||||
models::acs::mgm_assembly::request::Request::Ping => {
|
models::mgm_assembly::request::Request::Ping => {
|
||||||
self.send_telemetry(Some(tc_id), response::Response::Ok)
|
self.send_telemetry(Some(tc_id), response::Response::Ok)
|
||||||
}
|
}
|
||||||
models::acs::mgm_assembly::request::Request::Mode(request) => {
|
models::mgm_assembly::request::Request::Mode(request) => {
|
||||||
match request {
|
match request {
|
||||||
request::ModeRequest::SetMode(assembly_mode) => {
|
request::ModeRequest::SetMode(assembly_mode) => {
|
||||||
self.start_transition(false, assembly_mode, Some(tc_id))
|
self.start_transition(false, assembly_mode, Some(tc_id))
|
||||||
@@ -117,7 +117,7 @@ impl Assembly {
|
|||||||
pub fn send_telemetry(
|
pub fn send_telemetry(
|
||||||
&self,
|
&self,
|
||||||
tc_id: Option<CcsdsPacketIdAndPsc>,
|
tc_id: Option<CcsdsPacketIdAndPsc>,
|
||||||
response: models::acs::mgm_assembly::response::Response,
|
response: models::mgm_assembly::response::Response,
|
||||||
) {
|
) {
|
||||||
match pack_ccsds_tm_packet_for_now(Self::ID, tc_id, &response) {
|
match pack_ccsds_tm_packet_for_now(Self::ID, tc_id, &response) {
|
||||||
Ok(packet) => {
|
Ok(packet) => {
|
||||||
@@ -136,11 +136,11 @@ impl Assembly {
|
|||||||
match self.parent_queues.request_rx.try_recv() {
|
match self.parent_queues.request_rx.try_recv() {
|
||||||
Ok(request) => match request {
|
Ok(request) => match request {
|
||||||
request::ModeRequest::SetMode(assembly_mode) => match assembly_mode {
|
request::ModeRequest::SetMode(assembly_mode) => match assembly_mode {
|
||||||
Mode::Device(_device_mode) => {
|
AssemblyMode::Device(_device_mode) => {
|
||||||
self.start_transition(false, assembly_mode, None);
|
self.start_transition(false, assembly_mode, None);
|
||||||
}
|
}
|
||||||
Mode::NoModeKeeping => {
|
AssemblyMode::NoModeKeeping => {
|
||||||
self.mode_helper.current = Mode::NoModeKeeping;
|
self.mode_helper.current = AssemblyMode::NoModeKeeping;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
request::ModeRequest::ReadMode => self
|
request::ModeRequest::ReadMode => self
|
||||||
@@ -165,12 +165,12 @@ impl Assembly {
|
|||||||
loop {
|
loop {
|
||||||
match rx.try_recv() {
|
match rx.try_recv() {
|
||||||
Ok(report) => match report {
|
Ok(report) => match report {
|
||||||
models::acs::mgm::response::ModeResponse::Mode(device_mode) => {
|
models::mgm::response::ModeResponse::Mode(device_mode) => {
|
||||||
self.mgm_modes[idx].mode = Some(device_mode);
|
self.mgm_modes[idx].mode = Some(device_mode);
|
||||||
self.mgm_modes[idx].reply_received = true;
|
self.mgm_modes[idx].reply_received = true;
|
||||||
mode_report_received = true;
|
mode_report_received = true;
|
||||||
}
|
}
|
||||||
models::acs::mgm::response::ModeResponse::SetModeTimeout => {
|
models::mgm::response::ModeResponse::SetModeTimeout => {
|
||||||
// Ignore, handle this with our own timeout.
|
// Ignore, handle this with our own timeout.
|
||||||
log::warn!("MGM {} mode timeout", idx);
|
log::warn!("MGM {} mode timeout", idx);
|
||||||
}
|
}
|
||||||
@@ -191,7 +191,7 @@ impl Assembly {
|
|||||||
// Transition is active, check for completion.
|
// Transition is active, check for completion.
|
||||||
if self.mode_helper.transition_active()
|
if self.mode_helper.transition_active()
|
||||||
&& self.mgm_modes.iter().all(|i| i.reply_received)
|
&& self.mgm_modes.iter().all(|i| i.reply_received)
|
||||||
&& let Mode::Device(device_mode) = self.mode_helper.target.unwrap()
|
&& let AssemblyMode::Device(device_mode) = self.mode_helper.target.unwrap()
|
||||||
{
|
{
|
||||||
// If at least one child reached the correct mode, we are done.
|
// If at least one child reached the correct mode, we are done.
|
||||||
if self.mgm_modes.iter().any(|i| i.mode == Some(device_mode)) {
|
if self.mgm_modes.iter().any(|i| i.mode == Some(device_mode)) {
|
||||||
@@ -207,7 +207,7 @@ impl Assembly {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Mode keeping active: Check children modes.
|
// Mode keeping active: Check children modes.
|
||||||
if let Mode::Device(device_mode) = self.mode_helper.current
|
if let AssemblyMode::Device(device_mode) = self.mode_helper.current
|
||||||
&& self
|
&& self
|
||||||
.mgm_modes
|
.mgm_modes
|
||||||
.iter()
|
.iter()
|
||||||
@@ -226,8 +226,8 @@ impl Assembly {
|
|||||||
}
|
}
|
||||||
let target = self.mode_helper.target.unwrap();
|
let target = self.mode_helper.target.unwrap();
|
||||||
let device_mode = match target {
|
let device_mode = match target {
|
||||||
Mode::Device(device_mode) => device_mode,
|
AssemblyMode::Device(device_mode) => device_mode,
|
||||||
Mode::NoModeKeeping => {
|
AssemblyMode::NoModeKeeping => {
|
||||||
self.handle_mode_reached(true);
|
self.handle_mode_reached(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -275,7 +275,7 @@ impl Assembly {
|
|||||||
|
|
||||||
pub fn command_children(&self, mode: DeviceMode) {
|
pub fn command_children(&self, mode: DeviceMode) {
|
||||||
for tx in &self.children_queues.request_tx_queues {
|
for tx in &self.children_queues.request_tx_queues {
|
||||||
tx.send(models::acs::mgm::request::ModeRequest::SetMode(mode))
|
tx.send(models::mgm::request::ModeRequest::SetMode(mode))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -283,7 +283,7 @@ impl Assembly {
|
|||||||
pub fn start_transition(
|
pub fn start_transition(
|
||||||
&mut self,
|
&mut self,
|
||||||
mode_keeping: bool,
|
mode_keeping: bool,
|
||||||
target: Mode,
|
target: AssemblyMode,
|
||||||
tc_id: Option<CcsdsPacketIdAndPsc>,
|
tc_id: Option<CcsdsPacketIdAndPsc>,
|
||||||
) {
|
) {
|
||||||
self.mode_keeping_transition = mode_keeping;
|
self.mode_keeping_transition = mode_keeping;
|
||||||
@@ -304,7 +304,7 @@ impl Assembly {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn mode(&self) -> Mode {
|
pub fn mode(&self) -> AssemblyMode {
|
||||||
self.mode_helper.current
|
self.mode_helper.current
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -322,8 +322,8 @@ mod tests {
|
|||||||
use arbitrary_int::u11;
|
use arbitrary_int::u11;
|
||||||
use models::{
|
use models::{
|
||||||
Apid, Message, MessageType, TcHeader,
|
Apid, Message, MessageType, TcHeader,
|
||||||
acs::mgm_assembly,
|
|
||||||
ccsds::{CcsdsTcPacketOwned, CcsdsTmPacketOwned},
|
ccsds::{CcsdsTcPacketOwned, CcsdsTmPacketOwned},
|
||||||
|
mgm_assembly,
|
||||||
};
|
};
|
||||||
use satrs::spacepackets::SpacePacketHeader;
|
use satrs::spacepackets::SpacePacketHeader;
|
||||||
|
|
||||||
@@ -332,8 +332,8 @@ mod tests {
|
|||||||
pub struct Testbench {
|
pub struct Testbench {
|
||||||
subsystem_req_tx: mpsc::SyncSender<request::ModeRequest>,
|
subsystem_req_tx: mpsc::SyncSender<request::ModeRequest>,
|
||||||
subsystem_report_rx: mpsc::Receiver<response::ModeReport>,
|
subsystem_report_rx: mpsc::Receiver<response::ModeReport>,
|
||||||
mgm_request_rx: [mpsc::Receiver<models::acs::mgm::request::ModeRequest>; 2],
|
mgm_request_rx: [mpsc::Receiver<models::mgm::request::ModeRequest>; 2],
|
||||||
mgm_report_tx: [mpsc::SyncSender<models::acs::mgm::response::ModeResponse>; 2],
|
mgm_report_tx: [mpsc::SyncSender<models::mgm::response::ModeResponse>; 2],
|
||||||
tc_tx: mpsc::SyncSender<CcsdsTcPacketOwned>,
|
tc_tx: mpsc::SyncSender<CcsdsTcPacketOwned>,
|
||||||
tm_rx: mpsc::Receiver<CcsdsTmPacketOwned>,
|
tm_rx: mpsc::Receiver<CcsdsTmPacketOwned>,
|
||||||
assembly: Assembly,
|
assembly: Assembly,
|
||||||
@@ -396,7 +396,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_request_tc(
|
pub fn create_request_tc(
|
||||||
request: models::acs::mgm_assembly::request::Request,
|
request: models::mgm_assembly::request::Request,
|
||||||
) -> models::ccsds::CcsdsTcPacketOwned {
|
) -> models::ccsds::CcsdsTcPacketOwned {
|
||||||
models::ccsds::CcsdsTcPacketOwned::new_with_request(
|
models::ccsds::CcsdsTcPacketOwned::new_with_request(
|
||||||
SpacePacketHeader::new_from_apid(u11::new(Apid::Acs as u16)),
|
SpacePacketHeader::new_from_apid(u11::new(Apid::Acs as u16)),
|
||||||
@@ -411,7 +411,7 @@ mod tests {
|
|||||||
tb.assert_all_queues_empty();
|
tb.assert_all_queues_empty();
|
||||||
tb.assembly.periodic_operation();
|
tb.assembly.periodic_operation();
|
||||||
tb.assert_all_queues_empty();
|
tb.assert_all_queues_empty();
|
||||||
assert_eq!(tb.assembly.mode(), Mode::NoModeKeeping);
|
assert_eq!(tb.assembly.mode(), AssemblyMode::NoModeKeeping);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -419,7 +419,7 @@ mod tests {
|
|||||||
let mut tb = Testbench::new();
|
let mut tb = Testbench::new();
|
||||||
tb.tc_tx
|
tb.tc_tx
|
||||||
.send(create_request_tc(mgm_assembly::request::Request::Mode(
|
.send(create_request_tc(mgm_assembly::request::Request::Mode(
|
||||||
request::ModeRequest::SetMode(Mode::Device(DeviceMode::Normal)),
|
request::ModeRequest::SetMode(AssemblyMode::Device(DeviceMode::Normal)),
|
||||||
)))
|
)))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
tb.assembly.periodic_operation();
|
tb.assembly.periodic_operation();
|
||||||
@@ -429,13 +429,13 @@ mod tests {
|
|||||||
let request = rx.try_recv().unwrap();
|
let request = rx.try_recv().unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
request,
|
request,
|
||||||
models::acs::mgm::request::ModeRequest::SetMode(DeviceMode::Normal)
|
models::mgm::request::ModeRequest::SetMode(DeviceMode::Normal)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Confirm the mode is set.
|
// Confirm the mode is set.
|
||||||
for tx in tb.mgm_report_tx.iter() {
|
for tx in tb.mgm_report_tx.iter() {
|
||||||
tx.send(models::acs::mgm::response::ModeResponse::Mode(
|
tx.send(models::mgm::response::ModeResponse::Mode(
|
||||||
DeviceMode::Normal,
|
DeviceMode::Normal,
|
||||||
))
|
))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -443,7 +443,7 @@ mod tests {
|
|||||||
|
|
||||||
tb.assembly.periodic_operation();
|
tb.assembly.periodic_operation();
|
||||||
assert!(!tb.assembly.mode_transition_active());
|
assert!(!tb.assembly.mode_transition_active());
|
||||||
assert_eq!(tb.assembly.mode(), Mode::Device(DeviceMode::Normal));
|
assert_eq!(tb.assembly.mode(), AssemblyMode::Device(DeviceMode::Normal));
|
||||||
|
|
||||||
let response = tb.tm_rx.try_recv().unwrap();
|
let response = tb.tm_rx.try_recv().unwrap();
|
||||||
assert_eq!(response.tm_header.sender_id, Assembly::ID);
|
assert_eq!(response.tm_header.sender_id, Assembly::ID);
|
||||||
@@ -456,7 +456,7 @@ mod tests {
|
|||||||
fn test_parent_commanded_transition() {
|
fn test_parent_commanded_transition() {
|
||||||
let mut tb = Testbench::new();
|
let mut tb = Testbench::new();
|
||||||
tb.subsystem_req_tx
|
tb.subsystem_req_tx
|
||||||
.send(request::ModeRequest::SetMode(Mode::Device(
|
.send(request::ModeRequest::SetMode(AssemblyMode::Device(
|
||||||
DeviceMode::Normal,
|
DeviceMode::Normal,
|
||||||
)))
|
)))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -467,13 +467,13 @@ mod tests {
|
|||||||
let request = rx.try_recv().unwrap();
|
let request = rx.try_recv().unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
request,
|
request,
|
||||||
models::acs::mgm::request::ModeRequest::SetMode(DeviceMode::Normal)
|
models::mgm::request::ModeRequest::SetMode(DeviceMode::Normal)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Confirm the mode is set.
|
// Confirm the mode is set.
|
||||||
for tx in tb.mgm_report_tx.iter() {
|
for tx in tb.mgm_report_tx.iter() {
|
||||||
tx.send(models::acs::mgm::response::ModeResponse::Mode(
|
tx.send(models::mgm::response::ModeResponse::Mode(
|
||||||
DeviceMode::Normal,
|
DeviceMode::Normal,
|
||||||
))
|
))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -481,12 +481,12 @@ mod tests {
|
|||||||
|
|
||||||
tb.assembly.periodic_operation();
|
tb.assembly.periodic_operation();
|
||||||
assert!(!tb.assembly.mode_transition_active());
|
assert!(!tb.assembly.mode_transition_active());
|
||||||
assert_eq!(tb.assembly.mode(), Mode::Device(DeviceMode::Normal));
|
assert_eq!(tb.assembly.mode(), AssemblyMode::Device(DeviceMode::Normal));
|
||||||
|
|
||||||
let report = tb.subsystem_report_rx.try_recv().unwrap();
|
let report = tb.subsystem_report_rx.try_recv().unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
report,
|
report,
|
||||||
response::ModeReport::Mode(Mode::Device(DeviceMode::Normal))
|
response::ModeReport::Mode(AssemblyMode::Device(DeviceMode::Normal))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -494,7 +494,7 @@ mod tests {
|
|||||||
fn test_one_mgm_is_sufficient() {
|
fn test_one_mgm_is_sufficient() {
|
||||||
let mut tb = Testbench::new();
|
let mut tb = Testbench::new();
|
||||||
tb.subsystem_req_tx
|
tb.subsystem_req_tx
|
||||||
.send(request::ModeRequest::SetMode(Mode::Device(
|
.send(request::ModeRequest::SetMode(AssemblyMode::Device(
|
||||||
DeviceMode::Normal,
|
DeviceMode::Normal,
|
||||||
)))
|
)))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -505,30 +505,28 @@ mod tests {
|
|||||||
let request = rx.try_recv().unwrap();
|
let request = rx.try_recv().unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
request,
|
request,
|
||||||
models::acs::mgm::request::ModeRequest::SetMode(DeviceMode::Normal)
|
models::mgm::request::ModeRequest::SetMode(DeviceMode::Normal)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// One device is sufficient.
|
// One device is sufficient.
|
||||||
tb.mgm_report_tx[0]
|
tb.mgm_report_tx[0]
|
||||||
.send(models::acs::mgm::response::ModeResponse::Mode(
|
.send(models::mgm::response::ModeResponse::Mode(
|
||||||
DeviceMode::Normal,
|
DeviceMode::Normal,
|
||||||
))
|
))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
tb.mgm_report_tx[1]
|
tb.mgm_report_tx[1]
|
||||||
.send(models::acs::mgm::response::ModeResponse::Mode(
|
.send(models::mgm::response::ModeResponse::Mode(DeviceMode::Off))
|
||||||
DeviceMode::Off,
|
|
||||||
))
|
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
tb.assembly.periodic_operation();
|
tb.assembly.periodic_operation();
|
||||||
assert!(!tb.assembly.mode_transition_active());
|
assert!(!tb.assembly.mode_transition_active());
|
||||||
assert_eq!(tb.assembly.mode(), Mode::Device(DeviceMode::Normal));
|
assert_eq!(tb.assembly.mode(), AssemblyMode::Device(DeviceMode::Normal));
|
||||||
|
|
||||||
let report = tb.subsystem_report_rx.try_recv().unwrap();
|
let report = tb.subsystem_report_rx.try_recv().unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
report,
|
report,
|
||||||
response::ModeReport::Mode(Mode::Device(DeviceMode::Normal))
|
response::ModeReport::Mode(AssemblyMode::Device(DeviceMode::Normal))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -536,7 +534,7 @@ mod tests {
|
|||||||
fn test_mode_commanding_fails() {
|
fn test_mode_commanding_fails() {
|
||||||
let mut tb = Testbench::new();
|
let mut tb = Testbench::new();
|
||||||
tb.subsystem_req_tx
|
tb.subsystem_req_tx
|
||||||
.send(request::ModeRequest::SetMode(Mode::Device(
|
.send(request::ModeRequest::SetMode(AssemblyMode::Device(
|
||||||
DeviceMode::Normal,
|
DeviceMode::Normal,
|
||||||
)))
|
)))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -547,21 +545,19 @@ mod tests {
|
|||||||
let request = rx.try_recv().unwrap();
|
let request = rx.try_recv().unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
request,
|
request,
|
||||||
models::acs::mgm::request::ModeRequest::SetMode(DeviceMode::Normal)
|
models::mgm::request::ModeRequest::SetMode(DeviceMode::Normal)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Confirm the mode is set.
|
// Confirm the mode is set.
|
||||||
for tx in tb.mgm_report_tx.iter() {
|
for tx in tb.mgm_report_tx.iter() {
|
||||||
tx.send(models::acs::mgm::response::ModeResponse::Mode(
|
tx.send(models::mgm::response::ModeResponse::Mode(DeviceMode::Off))
|
||||||
DeviceMode::Off,
|
.unwrap();
|
||||||
))
|
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tb.assembly.periodic_operation();
|
tb.assembly.periodic_operation();
|
||||||
assert!(!tb.assembly.mode_transition_active());
|
assert!(!tb.assembly.mode_transition_active());
|
||||||
assert_eq!(tb.assembly.mode(), Mode::NoModeKeeping);
|
assert_eq!(tb.assembly.mode(), AssemblyMode::NoModeKeeping);
|
||||||
|
|
||||||
let report = tb.subsystem_report_rx.try_recv().unwrap();
|
let report = tb.subsystem_report_rx.try_recv().unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -574,7 +570,7 @@ mod tests {
|
|||||||
fn test_mode_keeping_fails() {
|
fn test_mode_keeping_fails() {
|
||||||
let mut tb = Testbench::new();
|
let mut tb = Testbench::new();
|
||||||
tb.subsystem_req_tx
|
tb.subsystem_req_tx
|
||||||
.send(request::ModeRequest::SetMode(Mode::Device(
|
.send(request::ModeRequest::SetMode(AssemblyMode::Device(
|
||||||
DeviceMode::Normal,
|
DeviceMode::Normal,
|
||||||
)))
|
)))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -585,13 +581,13 @@ mod tests {
|
|||||||
let request = rx.try_recv().unwrap();
|
let request = rx.try_recv().unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
request,
|
request,
|
||||||
models::acs::mgm::request::ModeRequest::SetMode(DeviceMode::Normal)
|
models::mgm::request::ModeRequest::SetMode(DeviceMode::Normal)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Confirm the mode is set.
|
// Confirm the mode is set.
|
||||||
for tx in tb.mgm_report_tx.iter() {
|
for tx in tb.mgm_report_tx.iter() {
|
||||||
tx.send(models::acs::mgm::response::ModeResponse::Mode(
|
tx.send(models::mgm::response::ModeResponse::Mode(
|
||||||
DeviceMode::Normal,
|
DeviceMode::Normal,
|
||||||
))
|
))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -599,19 +595,17 @@ mod tests {
|
|||||||
|
|
||||||
tb.assembly.periodic_operation();
|
tb.assembly.periodic_operation();
|
||||||
assert!(!tb.assembly.mode_transition_active());
|
assert!(!tb.assembly.mode_transition_active());
|
||||||
assert_eq!(tb.assembly.mode(), Mode::Device(DeviceMode::Normal));
|
assert_eq!(tb.assembly.mode(), AssemblyMode::Device(DeviceMode::Normal));
|
||||||
|
|
||||||
let report = tb.subsystem_report_rx.try_recv().unwrap();
|
let report = tb.subsystem_report_rx.try_recv().unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
report,
|
report,
|
||||||
response::ModeReport::Mode(Mode::Device(DeviceMode::Normal))
|
response::ModeReport::Mode(AssemblyMode::Device(DeviceMode::Normal))
|
||||||
);
|
);
|
||||||
|
|
||||||
for tx in tb.mgm_report_tx.iter() {
|
for tx in tb.mgm_report_tx.iter() {
|
||||||
tx.send(models::acs::mgm::response::ModeResponse::Mode(
|
tx.send(models::mgm::response::ModeResponse::Mode(DeviceMode::Off))
|
||||||
DeviceMode::Off,
|
.unwrap();
|
||||||
))
|
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
// This should start mode keeping.
|
// This should start mode keeping.
|
||||||
tb.assembly.periodic_operation();
|
tb.assembly.periodic_operation();
|
||||||
@@ -621,16 +615,14 @@ mod tests {
|
|||||||
let request = rx.try_recv().unwrap();
|
let request = rx.try_recv().unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
request,
|
request,
|
||||||
models::acs::mgm::request::ModeRequest::SetMode(DeviceMode::Normal)
|
models::mgm::request::ModeRequest::SetMode(DeviceMode::Normal)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Let the mode keeping fail.
|
// Let the mode keeping fail.
|
||||||
for tx in tb.mgm_report_tx.iter() {
|
for tx in tb.mgm_report_tx.iter() {
|
||||||
tx.send(models::acs::mgm::response::ModeResponse::Mode(
|
tx.send(models::mgm::response::ModeResponse::Mode(DeviceMode::Off))
|
||||||
DeviceMode::Off,
|
.unwrap();
|
||||||
))
|
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
tb.assembly.periodic_operation();
|
tb.assembly.periodic_operation();
|
||||||
let report = tb.subsystem_report_rx.try_recv().unwrap();
|
let report = tb.subsystem_report_rx.try_recv().unwrap();
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
pub mod ctrl;
|
pub mod ctrl;
|
||||||
|
|
||||||
pub mod mgm;
|
pub mod mgm;
|
||||||
pub mod mgm_assembly;
|
pub mod mgm_assembly;
|
||||||
pub mod mgt;
|
|
||||||
pub mod subsystem;
|
pub mod subsystem;
|
||||||
|
|||||||
@@ -1,116 +1 @@
|
|||||||
use std::sync::mpsc::SyncSender;
|
// TODO: Write subsystem
|
||||||
|
|
||||||
use models::ComponentId;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct TransitionInfo {
|
|
||||||
check_mode_reached: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct ChildModes {
|
|
||||||
mgm_assembly_mode: models::acs::mgm_assembly::Mode,
|
|
||||||
mgt_mode: models::acs::mgt::Mode,
|
|
||||||
controller_mode: models::acs::ctrl::Mode,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct TransitionCommands {
|
|
||||||
mgm_assembly_mode: Option<(models::acs::mgm_assembly::Mode, TransitionInfo)>,
|
|
||||||
mgt_mode: Option<(models::acs::mgt::Mode, TransitionInfo)>,
|
|
||||||
controller_mode: Option<(models::acs::ctrl::Mode, TransitionInfo)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
const OFF_SEQUENCE: [TransitionCommands; 2] = [
|
|
||||||
TransitionCommands {
|
|
||||||
mgm_assembly_mode: None,
|
|
||||||
mgt_mode: None,
|
|
||||||
controller_mode: Some((
|
|
||||||
models::acs::ctrl::Mode::Passive,
|
|
||||||
TransitionInfo {
|
|
||||||
check_mode_reached: true,
|
|
||||||
},
|
|
||||||
)),
|
|
||||||
},
|
|
||||||
TransitionCommands {
|
|
||||||
mgm_assembly_mode: Some((
|
|
||||||
models::acs::mgm_assembly::Mode::Device(models::DeviceMode::Off),
|
|
||||||
TransitionInfo {
|
|
||||||
check_mode_reached: false,
|
|
||||||
},
|
|
||||||
)),
|
|
||||||
mgt_mode: Some((
|
|
||||||
models::acs::mgt::Mode::Off,
|
|
||||||
TransitionInfo {
|
|
||||||
check_mode_reached: false,
|
|
||||||
},
|
|
||||||
)),
|
|
||||||
controller_mode: None,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const SAFE_SEQUENCE: [TransitionCommands; 2] = [
|
|
||||||
TransitionCommands {
|
|
||||||
mgm_assembly_mode: Some((
|
|
||||||
models::acs::mgm_assembly::Mode::Device(models::DeviceMode::Normal),
|
|
||||||
TransitionInfo {
|
|
||||||
check_mode_reached: false,
|
|
||||||
},
|
|
||||||
)),
|
|
||||||
mgt_mode: Some((
|
|
||||||
models::acs::mgt::Mode::Normal,
|
|
||||||
TransitionInfo {
|
|
||||||
check_mode_reached: false,
|
|
||||||
},
|
|
||||||
)),
|
|
||||||
controller_mode: None,
|
|
||||||
},
|
|
||||||
TransitionCommands {
|
|
||||||
mgm_assembly_mode: None,
|
|
||||||
mgt_mode: None,
|
|
||||||
controller_mode: Some((
|
|
||||||
models::acs::ctrl::Mode::Safe,
|
|
||||||
TransitionInfo {
|
|
||||||
check_mode_reached: false,
|
|
||||||
},
|
|
||||||
)),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct ModeRequestSenders {
|
|
||||||
mode_request_ctrl: SyncSender<models::acs::ctrl::request::ModeRequest>,
|
|
||||||
mode_request_assy: SyncSender<models::acs::mgm_assembly::request::ModeRequest>,
|
|
||||||
mode_request_mgt: SyncSender<models::acs::mgt::request::ModeRequest>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct ModeReportReceivers {
|
|
||||||
mode_response_ctrl: SyncSender<models::acs::ctrl::response::ModeReport>,
|
|
||||||
mode_response_assy: SyncSender<models::acs::mgm_assembly::response::ModeReport>,
|
|
||||||
mode_response_mgt: SyncSender<models::acs::mgt::response::ModeReport>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Subsystem {
|
|
||||||
current_mode: models::acs::subsystem::Mode,
|
|
||||||
current_child_modes: Option<ChildModes>,
|
|
||||||
mode_request_senders: ModeRequestSenders,
|
|
||||||
mode_report_receivers: ModeReportReceivers,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Subsystem {
|
|
||||||
pub const ID: ComponentId = ComponentId::AcsSubsystem;
|
|
||||||
|
|
||||||
pub fn new(
|
|
||||||
mode_request_senders: ModeRequestSenders,
|
|
||||||
mode_report_receivers: ModeReportReceivers,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
current_mode: models::acs::subsystem::Mode::Off,
|
|
||||||
current_child_modes: None,
|
|
||||||
mode_request_senders,
|
|
||||||
mode_report_receivers,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user