leodos_libcfs/app.rs
1//! Standard cFS application scaffolding.
2//!
3//! Provides an [`App`] builder that automates the common
4//! boilerplate every cFS app needs: pipe creation, topic
5//! subscription, EVS registration, NoOp/Reset command
6//! handling, and housekeeping telemetry.
7
8use crate::cfe::evs::event;
9use crate::cfe::sb::msg::{CmdHeader, MessageRef, MsgId, TlmHeader};
10use crate::cfe::sb::pipe::Pipe;
11use crate::cfe::sb::send_buf::SendBuffer;
12use crate::error::Result;
13
14/// Function code for NoOp command.
15const FCN_NOOP: u16 = 0;
16/// Function code for counter reset command.
17const FCN_RESET: u16 = 1;
18
19/// Event ID for NoOp command acknowledgement.
20const EVT_NOOP: u16 = 0;
21/// Event ID for counter reset acknowledgement.
22const EVT_RESET: u16 = 1;
23/// Event ID for invalid/unknown command code.
24const EVT_INVALID_CC: u16 = 2;
25/// Event ID for application startup.
26const EVT_STARTUP: u16 = 3;
27
28/// Base housekeeping telemetry payload.
29///
30/// Contains the standard cmd/err counters that every cFS
31/// app reports. Placed after the telemetry header in the
32/// HK packet.
33#[repr(C)]
34#[derive(Debug, Clone, Copy, Default)]
35pub struct HkTlm {
36 /// Total accepted commands since last reset.
37 pub cmd_count: u16,
38 /// Total rejected commands since last reset.
39 pub err_count: u16,
40}
41
42/// Event returned by [`App::recv`].
43pub enum Event<'a> {
44 /// An app-specific command (NoOp/Reset already handled).
45 Command(MessageRef<'a>),
46 /// HK wakeup — the app should publish telemetry.
47 Hk,
48}
49
50/// Standard cFS application with automatic boilerplate.
51///
52/// Handles pipe creation, topic subscriptions, EVS
53/// registration, NoOp (fcn 0), and Reset Counters (fcn 1).
54///
55/// HK wakeups are surfaced as [`Event::Hk`] so the app
56/// controls what telemetry to publish.
57///
58/// # Example
59///
60/// ```ignore
61/// let mut app = App::builder()
62/// .name("MY_APP")
63/// .cmd_topic(MY_CMD_TOPIC)
64/// .send_hk_topic(SEND_HK_TOPIC)
65/// .hk_tlm_topic(MY_HK_TLM_TOPIC)
66/// .version("1.0.0")
67/// .build()?;
68///
69/// let mut buf = [0u8; 256];
70/// loop {
71/// match app.recv(&mut buf).await? {
72/// Event::Hk => app.send_hk(&my_hk)?,
73/// Event::Command(msg) => app.reject(msg)?,
74/// }
75/// }
76/// ```
77pub struct App {
78 pipe: Pipe,
79 version: &'static str,
80 cmd_msg_id: MsgId,
81 send_hk_msg_id: MsgId,
82 hk_tlm_msg_id: MsgId,
83 cmd_count: u16,
84 err_count: u16,
85}
86
87#[bon::bon]
88impl App {
89 /// Creates a new cFS application.
90 ///
91 /// Registers with EVS, creates a Software Bus pipe,
92 /// and subscribes to command and HK wakeup topics.
93 ///
94 /// Both `cmd_topic` and `send_hk_topic` are treated
95 /// as local command topic IDs. `hk_tlm_topic` is a
96 /// local telemetry topic ID.
97 #[builder]
98 pub fn new(
99 name: &'static str,
100 cmd_topic: u16,
101 send_hk_topic: u16,
102 hk_tlm_topic: u16,
103 version: &'static str,
104 #[builder(default = 16)] pipe_depth: u16,
105 ) -> Result<Self> {
106 event::register(&[])?;
107
108 let pipe = Pipe::new(name, pipe_depth)?;
109 let cmd_msg_id = MsgId::local_cmd(cmd_topic);
110 let send_hk_msg_id = MsgId::local_cmd(send_hk_topic);
111 let hk_tlm_msg_id = MsgId::local_tlm(hk_tlm_topic);
112
113 pipe.subscribe(cmd_msg_id)?;
114 pipe.subscribe(send_hk_msg_id)?;
115
116 event::info(EVT_STARTUP, version)?;
117
118 Ok(Self {
119 pipe,
120 version,
121 cmd_msg_id,
122 send_hk_msg_id,
123 hk_tlm_msg_id,
124 cmd_count: 0,
125 err_count: 0,
126 })
127 }
128}
129
130impl App {
131 /// Processes commands and HK in a loop.
132 ///
133 /// Publishes base HK (cmd/err counters) on wakeup,
134 /// rejects unrecognized commands. For custom HK or
135 /// app-specific commands, use [`recv`](Self::recv).
136 pub async fn run(&mut self) -> Result<()> {
137 let mut buf = [0u8; 256];
138 loop {
139 match self.recv(&mut buf).await? {
140 Event::Hk => self.send_hk_base()?,
141 Event::Command(msg) => self.reject(msg)?,
142 }
143 }
144 }
145
146 /// Receives the next event from the Software Bus.
147 ///
148 /// Automatically handles NoOp (fcn 0) and Reset (fcn 1).
149 /// Returns [`Event::Hk`] on HK wakeup so the app can
150 /// publish its own telemetry. Returns [`Event::Command`]
151 /// for app-specific commands.
152 pub async fn recv<'a>(&mut self, buf: &'a mut [u8]) -> Result<Event<'a>> {
153 loop {
154 let len = self.pipe.recv(buf).await?;
155 let msg = MessageRef::new(&buf[..len]);
156 let msg_id = msg.msg_id()?;
157
158 if msg_id == self.send_hk_msg_id {
159 return Ok(Event::Hk);
160 }
161
162 if msg_id == self.cmd_msg_id {
163 let cmd_hdr_size = core::mem::size_of::<CmdHeader>();
164 match msg.fcn_code()? {
165 FCN_NOOP if msg.size()? == cmd_hdr_size => {
166 self.cmd_count = self.cmd_count.wrapping_add(1);
167 event::info(EVT_NOOP, self.version)?;
168 }
169 FCN_RESET if msg.size()? == cmd_hdr_size => {
170 self.cmd_count = 0;
171 self.err_count = 0;
172 event::info(EVT_RESET, "Counters reset")?;
173 }
174 FCN_NOOP | FCN_RESET => {
175 self.err_count = self.err_count.wrapping_add(1);
176 event::error(EVT_INVALID_CC, "Wrong message length")?;
177 }
178 _ => return Ok(Event::Command(MessageRef::new(&buf[..len]))),
179 }
180 }
181 }
182 }
183
184 /// Acknowledges successful command processing.
185 ///
186 /// Increments the command counter.
187 pub fn ack(&mut self) {
188 self.cmd_count = self.cmd_count.wrapping_add(1);
189 }
190
191 /// Rejects an unrecognized command.
192 ///
193 /// Increments the error counter and sends an error
194 /// event.
195 pub fn reject(&mut self, _msg: MessageRef<'_>) -> Result<()> {
196 self.err_count = self.err_count.wrapping_add(1);
197 event::error(EVT_INVALID_CC, "Invalid command code")
198 }
199
200 /// Publishes a custom HK telemetry packet.
201 ///
202 /// The payload is serialized via `as_bytes()`. The app
203 /// should include cmd/err counters (from
204 /// [`cmd_count`](Self::cmd_count) /
205 /// [`err_count`](Self::err_count)) in the struct.
206 pub fn send_hk<H: Copy>(&self, payload: &H) -> Result<()> {
207 let hdr = core::mem::size_of::<TlmHeader>();
208 let size = hdr + core::mem::size_of::<H>();
209
210 let mut send_buf = SendBuffer::new(size)?;
211 {
212 let mut msg = send_buf.view();
213 msg.init(self.hk_tlm_msg_id, size)?;
214 *msg.payload::<H>()? = *payload;
215 msg.timestamp();
216 }
217 send_buf.send(true)
218 }
219
220 /// Returns the current command counter.
221 pub fn cmd_count(&self) -> u16 {
222 self.cmd_count
223 }
224
225 /// Returns the current error counter.
226 pub fn err_count(&self) -> u16 {
227 self.err_count
228 }
229
230 /// Publishes base HK (cmd/err counters only).
231 fn send_hk_base(&self) -> Result<()> {
232 self.send_hk(&HkTlm {
233 cmd_count: self.cmd_count,
234 err_count: self.err_count,
235 })
236 }
237}