1use crate::{
2 command::{ATTITUDE_RATE_MODE, CommandManager},
3 events::ParamEventQueues,
4 packets::RcPacket,
5 params::{ParamId, ParamValue, Params},
6 rc::Rc,
7 state_machine::StateManager,
8};
9
10pub struct RcCommandStateCtx<'a> {
11 pub now_ms: u32,
12 pub fresh_rc: Option<RcPacket>,
16 pub rc: &'a mut Rc,
17 pub command: &'a mut CommandManager,
18 pub state: &'a mut StateManager,
19 pub params: &'a mut Params,
20 pub param_events: Option<&'a mut ParamEventQueues>,
21}
22
23pub fn run_rc_command_state(ctx: RcCommandStateCtx<'_>) {
24 if let Some(rc_packet) = ctx.fresh_rc {
25 ctx.rc.receive(&rc_packet);
26 ctx.rc.run(ctx.now_ms, ctx.params, ctx.state);
27 }
28
29 let command_result = ctx.command.run(ctx.now_ms, ctx.params, ctx.rc, ctx.state);
30 if command_result.force_rc_attitude_mode_rate {
31 if let Some(param_events) = ctx.param_events {
32 crate::params::service::set_param_and_emit_change(
33 ctx.params,
34 &mut param_events.changes,
35 ParamId::PARAM_RC_ATTITUDE_MODE,
36 ParamValue::Int(ATTITUDE_RATE_MODE),
37 );
38 } else {
39 ctx.params.set_by_id(
40 ParamId::PARAM_RC_ATTITUDE_MODE,
41 ParamValue::Int(ATTITUDE_RATE_MODE),
42 );
43 }
44 }
45 ctx.state.run(ctx.params);
46}
47
48#[cfg(test)]
49mod tests {
50 use super::*;
51 use crate::{
52 events::ParamEventQueues,
53 packets::{RC_PACKET_CHANNELS, RcPacket, RosflightPacketHeader},
54 params::{ParamId, ParamValue},
55 state_machine::{ErrorFlag, Event},
56 };
57
58 #[test]
59 fn rc_command_state_consumes_named_rc_packet() {
60 let mut params = Params::new();
61 params.set_by_id(ParamId::PARAM_RC_NUM_CHANNELS, ParamValue::Int(1));
62 let fresh_rc = Some(RcPacket {
63 header: RosflightPacketHeader {
64 timestamp: 1,
65 status: 0,
66 },
67 n_chan: 1,
68 chan: [0.5; RC_PACKET_CHANNELS],
69 lol: false,
70 });
71 let mut rc = Rc::new();
72 let mut command = CommandManager::new();
73 let mut state = StateManager::new();
74
75 run_rc_command_state(RcCommandStateCtx {
76 now_ms: 1,
77 fresh_rc,
78 rc: &mut rc,
79 command: &mut command,
80 state: &mut state,
81 params: &mut params,
82 param_events: None,
83 });
84
85 assert!(!state.get_errors().contains(ErrorFlag::RC_LOST));
86 }
87
88 #[test]
89 fn rc_command_state_does_not_publish_command_from_lost_frame() {
90 let mut params = Params::new();
91 params.set_by_id(ParamId::PARAM_RC_NUM_CHANNELS, ParamValue::Int(1));
92 let fresh_rc = Some(RcPacket {
93 header: RosflightPacketHeader {
94 timestamp: 1,
95 status: 0,
96 },
97 n_chan: 1,
98 chan: [0.5; RC_PACKET_CHANNELS],
99 lol: true,
100 });
101 let mut rc = Rc::new();
102 let mut command = CommandManager::new();
103 let mut state = StateManager::new();
104
105 run_rc_command_state(RcCommandStateCtx {
106 now_ms: 1,
107 fresh_rc,
108 rc: &mut rc,
109 command: &mut command,
110 state: &mut state,
111 params: &mut params,
112 param_events: None,
113 });
114
115 assert!(state.get_errors().contains(ErrorFlag::RC_LOST));
116 assert!(!rc.new_command());
117 }
118
119 #[test]
120 fn rc_command_state_does_not_treat_protocol_status_bits_as_link_loss() {
121 let mut params = Params::new();
122 params.set_by_id(ParamId::PARAM_RC_NUM_CHANNELS, ParamValue::Int(1));
123 let fresh_rc = Some(RcPacket {
124 header: RosflightPacketHeader {
125 timestamp: 1,
126 status: 0x03,
128 },
129 n_chan: 1,
130 chan: [0.5; RC_PACKET_CHANNELS],
131 lol: false,
132 });
133 let mut rc = Rc::new();
134 let mut command = CommandManager::new();
135 let mut state = StateManager::new();
136
137 run_rc_command_state(RcCommandStateCtx {
138 now_ms: 1,
139 fresh_rc,
140 rc: &mut rc,
141 command: &mut command,
142 state: &mut state,
143 params: &mut params,
144 param_events: None,
145 });
146
147 assert!(!state.get_errors().contains(ErrorFlag::RC_LOST));
148 }
149
150 #[test]
151 fn lost_frame_with_arm_channel_low_enters_failsafe_without_disarming() {
152 let mut params = Params::new();
153 params.set_by_id(ParamId::PARAM_RC_NUM_CHANNELS, ParamValue::Int(8));
154 params.set_by_id(ParamId::PARAM_RC_ARM_CHANNEL, ParamValue::Int(4));
155
156 let mut channels = [0.5; RC_PACKET_CHANNELS];
157 channels[2] = 0.0;
158 channels[4] = 0.0;
159 let fresh_rc = Some(RcPacket {
160 header: RosflightPacketHeader {
161 timestamp: 1,
162 status: 0,
163 },
164 n_chan: 8,
165 chan: channels,
166 lol: true,
167 });
168
169 let mut rc = Rc::new();
170 rc.init(¶ms);
171 let mut command = CommandManager::new();
172 let mut state = StateManager::new();
173 state.update(Event::INITIALIZED, ¶ms);
174 state.update_arming_safety(true, true);
175 state.update(Event::REQUEST_ARM, ¶ms);
176 assert!(state.is_armed());
177
178 run_rc_command_state(RcCommandStateCtx {
179 now_ms: 1,
180 fresh_rc,
181 rc: &mut rc,
182 command: &mut command,
183 state: &mut state,
184 params: &mut params,
185 param_events: None,
186 });
187
188 assert!(state.is_armed());
189 assert!(state.is_in_failsafe());
190 assert!(state.get_errors().contains(ErrorFlag::RC_LOST));
191 }
192
193 #[test]
194 fn rc_command_state_emits_param_change_when_lockout_forces_rate_mode() {
195 let mut params = Params::new();
196 params.set_by_id(ParamId::PARAM_RC_ATTITUDE_MODE, ParamValue::Int(1));
197 params.set_by_id(ParamId::PARAM_EST_ANGLE_LOCKOUT, ParamValue::Int(1));
198 let fresh_rc = Some(RcPacket {
199 header: RosflightPacketHeader {
200 timestamp: 1,
201 status: 0,
202 },
203 n_chan: 8,
204 chan: [0.5; RC_PACKET_CHANNELS],
205 lol: false,
206 });
207 let mut rc = Rc::new();
208 rc.init(¶ms);
209 let mut command = CommandManager::new();
210 let mut state = StateManager::new();
211 state.set_error_flag(ErrorFlag::UNHEALTHY_ESTIMATOR, true, ¶ms);
212 let mut param_events = ParamEventQueues::default();
213
214 run_rc_command_state(RcCommandStateCtx {
215 now_ms: 1,
216 fresh_rc,
217 rc: &mut rc,
218 command: &mut command,
219 state: &mut state,
220 params: &mut params,
221 param_events: Some(&mut param_events),
222 });
223
224 assert_eq!(
225 params.get_by_id(ParamId::PARAM_RC_ATTITUDE_MODE),
226 ParamValue::Int(ATTITUDE_RATE_MODE)
227 );
228 let change = param_events.changes.pop().unwrap();
229 assert_eq!(change.id, ParamId::PARAM_RC_ATTITUDE_MODE);
230 assert_eq!(change.old, ParamValue::Int(1));
231 assert_eq!(change.new, ParamValue::Int(ATTITUDE_RATE_MODE));
232 }
233
234 #[test]
235 fn rc_command_state_does_not_force_rate_mode_when_lockout_disabled() {
236 let mut params = Params::new();
237 params.set_by_id(ParamId::PARAM_RC_ATTITUDE_MODE, ParamValue::Int(1));
238 let fresh_rc = Some(RcPacket {
239 header: RosflightPacketHeader {
240 timestamp: 1,
241 status: 0,
242 },
243 n_chan: 8,
244 chan: [0.5; RC_PACKET_CHANNELS],
245 lol: false,
246 });
247 let mut rc = Rc::new();
248 rc.init(¶ms);
249 let mut command = CommandManager::new();
250 let mut state = StateManager::new();
251 state.set_error_flag(ErrorFlag::UNHEALTHY_ESTIMATOR, true, ¶ms);
252 let mut param_events = ParamEventQueues::default();
253
254 run_rc_command_state(RcCommandStateCtx {
255 now_ms: 1,
256 fresh_rc,
257 rc: &mut rc,
258 command: &mut command,
259 state: &mut state,
260 params: &mut params,
261 param_events: Some(&mut param_events),
262 });
263
264 assert_eq!(
265 params.get_by_id(ParamId::PARAM_RC_ATTITUDE_MODE),
266 ParamValue::Int(1)
267 );
268 assert!(param_events.changes.pop().is_none());
269 }
270}