Skip to main content

veloxity_core/
rc.rs

1use crate::log_info;
2use crate::packets::RcPacket;
3use crate::params::{ParamId, ParamValue, Params};
4use crate::state_machine::{ErrorFlag, Event, StateManager};
5
6pub mod command_state;
7
8// --- Constants ---
9pub const STICKS_COUNT: usize = 4;
10pub const SWITCHES_COUNT: usize = 5;
11pub const RC_STRUCT_CHANNELS: usize = 16; // A common max channel count
12
13const RC_TIMEOUT_US: u64 = 500_000;
14
15// --- Enums ---
16
17#[repr(usize)]
18#[derive(Debug, Copy, Clone, Eq, PartialEq)]
19pub enum Stick {
20    X = 0,
21    Y = 1,
22    Z = 2,
23    F = 3,
24}
25
26#[repr(usize)]
27#[derive(Debug, Copy, Clone, Eq, PartialEq)]
28pub enum Switch {
29    Arm = 0,
30    AttOverride = 1,
31    ThrottleOverride = 2,
32    AttType = 3,
33    OutputKill = 4,
34}
35
36// --- RC Data Structs ---
37
38/// Header for raw RC data
39#[derive(Default, Debug, Copy, Clone)]
40pub struct RcHeader {
41    pub timestamp: u64, // Microseconds
42    pub status: u16,    // Bitfield, 0 = OK
43}
44
45#[derive(Default, Copy, Clone)]
46struct StickConfig {
47    channel: i32,
48    one_sided: bool,
49}
50
51#[derive(Default, Copy, Clone)]
52struct SwitchConfig {
53    channel: i32,
54    mapped: bool,
55    direction: i32,
56}
57
58pub struct RcStruct {
59    pub header: RcHeader,
60    pub chan: [f32; RC_STRUCT_CHANNELS],
61    pub num_channels: usize,
62    pub frame_lost: bool,
63    pub failsafe_activated: bool,
64}
65
66impl Default for RcStruct {
67    fn default() -> Self {
68        Self {
69            header: RcHeader::default(),
70            chan: [0.0; RC_STRUCT_CHANNELS],
71            num_channels: 0,
72            frame_lost: false,
73            failsafe_activated: false,
74        }
75    }
76}
77
78// --- Public RC Struct ---
79pub struct Rc {
80    rc: RcStruct, // Raw channel data
81    new_command: bool,
82
83    sticks: [StickConfig; STICKS_COUNT],
84    switches: [SwitchConfig; SWITCHES_COUNT],
85
86    stick_values: [f32; STICKS_COUNT],
87    switch_values: [bool; SWITCHES_COUNT],
88
89    prev_time_ms: u32,
90    time_sticks_have_been_in_arming_position_ms: u32,
91}
92
93impl Rc {
94    /// Creates a new, uninitialized RC handler
95    pub fn new() -> Self {
96        Self {
97            rc: RcStruct::default(),
98            new_command: false,
99            sticks: [StickConfig::default(); STICKS_COUNT],
100            switches: [SwitchConfig::default(); SWITCHES_COUNT],
101            stick_values: [0.0; STICKS_COUNT],
102            switch_values: [false; SWITCHES_COUNT],
103            prev_time_ms: 0,
104            time_sticks_have_been_in_arming_position_ms: 0,
105        }
106    }
107
108    /// Initializes RC internal mappings from parameters.
109    pub fn init(&mut self, params: &Params) {
110        self.init_rc(params);
111        self.new_command = false;
112    }
113
114    fn init_rc(&mut self, params: &Params) {
115        self.init_sticks(params);
116        self.update_switch_mappings(params);
117    }
118
119    // Maps stick parameters to internal config
120    fn init_sticks(&mut self, params: &Params) {
121        // --- REFACTORED: Stick::X ---
122        self.sticks[Stick::X as usize] = StickConfig {
123            channel: match params.get_by_id(ParamId::PARAM_RC_X_CHANNEL) {
124                ParamValue::Int(val) => val,
125                _ => {
126                    0 // Default C++ value
127                }
128            },
129            one_sided: false,
130        };
131
132        // --- REFACTORED: Stick::Y ---
133        self.sticks[Stick::Y as usize] = StickConfig {
134            channel: match params.get_by_id(ParamId::PARAM_RC_Y_CHANNEL) {
135                ParamValue::Int(val) => val,
136                _ => {
137                    1 // Default C++ value
138                }
139            },
140            one_sided: false,
141        };
142
143        // --- REFACTORED: Stick::Z ---
144        self.sticks[Stick::Z as usize] = StickConfig {
145            channel: match params.get_by_id(ParamId::PARAM_RC_Z_CHANNEL) {
146                ParamValue::Int(val) => val,
147                _ => {
148                    3 // Default C++ value
149                }
150            },
151            one_sided: false,
152        };
153
154        // --- REFACTORED: Stick::F ---
155        self.sticks[Stick::F as usize] = StickConfig {
156            channel: match params.get_by_id(ParamId::PARAM_RC_F_CHANNEL) {
157                ParamValue::Int(val) => val,
158                _ => {
159                    2 // Default C++ value
160                }
161            },
162            one_sided: true,
163        };
164    }
165
166    fn update_switch_mappings(&mut self, params: &Params) {
167        // --- REFACTORED: PARAM_RC_NUM_CHANNELS ---
168        let rc_num_channels = match params.get_by_id(ParamId::PARAM_RC_NUM_CHANNELS) {
169            ParamValue::Int(val) => val,
170            _ => {
171                6 // Default C++ value
172            }
173        };
174
175        // must loop over the 4 logical functions we have defined
176        for i in 0..SWITCHES_COUNT {
177            // Using Option<ParamId> to handle the "INVALID" case safely
178            let channel_param_id = match i {
179                i if i == Switch::Arm as usize => Some(ParamId::PARAM_RC_ARM_CHANNEL),
180                i if i == Switch::AttOverride as usize => {
181                    Some(ParamId::PARAM_RC_ATTITUDE_OVERRIDE_CHANNEL)
182                }
183                i if i == Switch::ThrottleOverride as usize => {
184                    Some(ParamId::PARAM_RC_THROTTLE_OVERRIDE_CHANNEL)
185                }
186                i if i == Switch::AttType as usize => {
187                    Some(ParamId::PARAM_RC_ATT_CONTROL_TYPE_CHANNEL)
188                }
189                i if i == Switch::OutputKill as usize => {
190                    Some(ParamId::PARAM_RC_OUTPUT_KILL_CHANNEL)
191                }
192                _ => None,
193            };
194
195            // --- REFACTORED: channel_num retrieval ---
196            let channel_num = if let Some(id) = channel_param_id {
197                match params.get_by_id(id) {
198                    ParamValue::Int(val) => val,
199                    _ => {
200                        255 // Default for "INVALID"
201                    }
202                }
203            } else {
204                255 // C++ default for "INVALID"
205            };
206
207            self.switches[i].channel = channel_num;
208            self.switches[i].mapped = channel_num > 3 && channel_num < rc_num_channels;
209
210            let direction_param_id = match channel_num {
211                4 => Some(ParamId::PARAM_RC_SWITCH_5_DIRECTION),
212                5 => Some(ParamId::PARAM_RC_SWITCH_6_DIRECTION),
213                6 => Some(ParamId::PARAM_RC_SWITCH_7_DIRECTION),
214                7 => Some(ParamId::PARAM_RC_SWITCH_8_DIRECTION),
215                _ => None, // No param
216            };
217
218            // --- REFACTORED: direction retrieval ---
219            self.switches[i].direction = if let Some(id) = direction_param_id {
220                match params.get_by_id(id) {
221                    ParamValue::Int(val) => val,
222                    _ => {
223                        1 // C++ default
224                    }
225                }
226            } else {
227                1 // C++ default
228            };
229        }
230    }
231
232    fn log_switch_mappings(&self) {
233        for i in 0..SWITCHES_COUNT {
234            let (channel_name, _) = match i {
235                i if i == Switch::Arm as usize => ("ARM", Some(ParamId::PARAM_RC_ARM_CHANNEL)),
236                i if i == Switch::AttOverride as usize => (
237                    "ATTITUDE OVERRIDE",
238                    Some(ParamId::PARAM_RC_ATTITUDE_OVERRIDE_CHANNEL),
239                ),
240                i if i == Switch::ThrottleOverride as usize => (
241                    "THROTTLE OVERRIDE",
242                    Some(ParamId::PARAM_RC_THROTTLE_OVERRIDE_CHANNEL),
243                ),
244                i if i == Switch::AttType as usize => (
245                    "ATTITUDE TYPE",
246                    Some(ParamId::PARAM_RC_ATT_CONTROL_TYPE_CHANNEL),
247                ),
248                i if i == Switch::OutputKill as usize => {
249                    ("OUTPUT KILL", Some(ParamId::PARAM_RC_OUTPUT_KILL_CHANNEL))
250                }
251                _ => ("INVALID", None),
252            };
253
254            if channel_name == "INVALID" {
255                continue;
256            }
257
258            if self.switches[i].mapped {
259                log_info!(
260                    "{} switch mapped to RC Channel {}",
261                    channel_name,
262                    self.switches[i].channel
263                );
264            } else {
265                // comm.log(
266                //     LogSeverity::LOG_INFO,
267                //     &format!("{} switch not mapped", channel_name)
268                // );
269                log_info!("{} switch not mapped", channel_name);
270            }
271        }
272    }
273
274    pub fn param_change_callback(&mut self, param_id: ParamId, params: &Params) {
275        match param_id {
276            // ... (PARAM_RC_TYPE case is removed)
277            ParamId::PARAM_RC_X_CHANNEL
278            | ParamId::PARAM_RC_Y_CHANNEL
279            | ParamId::PARAM_RC_Z_CHANNEL
280            | ParamId::PARAM_RC_F_CHANNEL => {
281                self.init_sticks(params);
282            }
283            ParamId::PARAM_RC_ATTITUDE_OVERRIDE_CHANNEL
284            | ParamId::PARAM_RC_THROTTLE_OVERRIDE_CHANNEL
285            | ParamId::PARAM_RC_ATT_CONTROL_TYPE_CHANNEL
286            | ParamId::PARAM_RC_ARM_CHANNEL
287            | ParamId::PARAM_RC_OUTPUT_KILL_CHANNEL
288            | ParamId::PARAM_RC_SWITCH_5_DIRECTION
289            | ParamId::PARAM_RC_SWITCH_6_DIRECTION
290            | ParamId::PARAM_RC_SWITCH_7_DIRECTION
291            | ParamId::PARAM_RC_SWITCH_8_DIRECTION => {
292                self.update_switch_mappings(params); // <-- 1. Update mappings
293                self.log_switch_mappings(); // <-- 2. Log the changes
294            }
295            _ => {
296                // do nothing
297            }
298        }
299    }
300
301    pub fn receive(&mut self, packet: &RcPacket) {
302        // 1. Copy data from the packet into the internal rc_struct: We assume it has been normalized before this point
303        // Get the number of channels
304        let len = (packet.n_chan as usize).min(self.rc.chan.len());
305        self.rc.chan[..len].copy_from_slice(&packet.chan[..len]);
306
307        self.rc.header.timestamp = packet.header.timestamp;
308        self.rc.header.status = packet.header.status;
309        self.rc.num_channels = len;
310
311        // Link-loss encoding is receiver-protocol-specific. Board drivers
312        // decode their protocol's status flags into RcPacket::lol, so core RC
313        // handling must use that normalized signal instead of interpreting the
314        // raw header status bits. In SBUS, status bits 0 and 1 are digital
315        // channels 17 and 18; treating them as frame-lost/failsafe flags can
316        // both miss a real link loss and falsely reject healthy RC frames.
317        self.rc.frame_lost = packet.lol;
318        self.rc.failsafe_activated = packet.lol;
319    }
320
321    fn process_sticks_and_switches(&mut self) {
322        // STICKS
323        for channel in 0..STICKS_COUNT {
324            let config = &self.sticks[channel];
325            if config.channel < 0 || (config.channel as usize) >= self.rc.num_channels {
326                continue;
327            }
328            let pwm = self.rc.chan[config.channel as usize]; // pwm is 0.0 to 1.0
329
330            if config.one_sided {
331                // generally only F
332                self.stick_values[channel] = pwm;
333            } else {
334                // Converts [0.0, 1.0] to [-1.0, 1.0]
335                self.stick_values[channel] = 2.0 * (pwm - 0.5);
336            }
337        }
338
339        // SWITCHES
340
341        for channel in 0..SWITCHES_COUNT {
342            let config = &self.switches[channel];
343            if config.mapped {
344                if config.channel < 0 || (config.channel as usize) >= self.rc.num_channels {
345                    self.switch_values[channel] = false;
346                    continue;
347                }
348
349                let pwm = self.rc.chan[config.channel as usize]; // pwm is 0.0 to 1.0
350
351                if config.direction < 0 {
352                    self.switch_values[channel] = pwm < 0.2; // C++ logic
353                } else {
354                    self.switch_values[channel] = pwm >= 0.8; // C++ logic
355                }
356            } else {
357                self.switch_values[channel] = false;
358            }
359        }
360    }
361
362    pub fn check_rc_health(&self, now_us: u64, params: &Params) -> bool {
363        if now_us > self.rc.header.timestamp + RC_TIMEOUT_US {
364            return false;
365        }
366
367        if self.rc.frame_lost || self.rc.failsafe_activated {
368            return false;
369        }
370
371        let num_channels = match params.get_by_id(ParamId::PARAM_RC_NUM_CHANNELS) {
372            ParamValue::Int(val) => val as usize,
373            _ => 6,
374        };
375        if self.rc.num_channels < num_channels {
376            return false;
377        }
378
379        for i in 0..num_channels {
380            let val = self.rc.chan[i];
381            if val < -0.25 || val > 1.25 {
382                return false;
383            }
384        }
385        return true;
386    }
387
388    pub fn run(
389        &mut self,
390        now_ms: u32,
391        params: &Params,
392        state_manager: &mut StateManager, // Use the concrete StateManager
393    ) {
394        let now_us = (now_ms as u64) * 1000;
395
396        if self.check_rc_health(now_us, params) {
397            state_manager.update(Event::ERROR_CLEARED(ErrorFlag::RC_LOST), params);
398
399            self.process_sticks_and_switches();
400            self.new_command = true;
401
402            // only run arming logic if rc is healthy
403            self.look_for_arm_disarm_signal(now_ms, params, state_manager);
404        } else {
405            self.new_command = false;
406            state_manager.update(Event::ERROR_OCCURRED(ErrorFlag::RC_LOST), params);
407        }
408    }
409
410    /// Checks for stick or switch arming/disarming
411    fn look_for_arm_disarm_signal(
412        &mut self,
413        now_ms: u32,
414        params: &Params,
415        state_manager: &mut StateManager, // Use the concrete StateManager
416    ) {
417        let dt = now_ms.saturating_sub(self.prev_time_ms);
418        self.prev_time_ms = now_ms;
419
420        let arm_threshold = match params.get_by_id(ParamId::PARAM_ARM_THRESHOLD) {
421            ParamValue::Float(val) => val,
422            _ => {
423                0.15 // Default value from C++ param definitions
424            }
425        };
426
427        // Use the correct public method from StateManager
428        let is_armed = state_manager.is_armed();
429
430        if !self.switch_mapped(Switch::Arm) {
431            // Stick arming
432            let f_stick = self.stick(Stick::F);
433            let z_stick = self.stick(Stick::Z);
434
435            if !is_armed {
436                // DISARMED
437                // if left stick is down and to the right
438                if f_stick < arm_threshold && z_stick > (1.0 - arm_threshold) {
439                    self.time_sticks_have_been_in_arming_position_ms = self
440                        .time_sticks_have_been_in_arming_position_ms
441                        .saturating_add(dt);
442                } else {
443                    self.time_sticks_have_been_in_arming_position_ms = 0;
444                }
445
446                if self.time_sticks_have_been_in_arming_position_ms > 1000 {
447                    state_manager.update_arming_safety(
448                        f_stick < arm_threshold,
449                        self.switch_mapped(Switch::ThrottleOverride)
450                            && self.switch_on(Switch::ThrottleOverride),
451                    );
452                    // Use update() with params
453                    state_manager.update(Event::REQUEST_ARM, params);
454                }
455            } else {
456                // ARMED
457                // if left stick is down and to the left
458                if f_stick < arm_threshold && z_stick < -(1.0 - arm_threshold) {
459                    self.time_sticks_have_been_in_arming_position_ms = self
460                        .time_sticks_have_been_in_arming_position_ms
461                        .saturating_add(dt);
462                } else {
463                    self.time_sticks_have_been_in_arming_position_ms = 0;
464                }
465
466                if self.time_sticks_have_been_in_arming_position_ms > 1000 {
467                    // Use update() with params
468                    state_manager.update(Event::REQUEST_DISARM, params);
469                    self.time_sticks_have_been_in_arming_position_ms = 0;
470                }
471            }
472        } else {
473            // Switch arming
474            let f_stick = self.stick(Stick::F);
475            if self.switch_on(Switch::Arm) {
476                state_manager.update_arming_safety(
477                    f_stick < arm_threshold,
478                    self.switch_mapped(Switch::ThrottleOverride)
479                        && self.switch_on(Switch::ThrottleOverride),
480                );
481                if !is_armed {
482                    // Use update() with params
483                    state_manager.update(Event::REQUEST_ARM, params);
484                }
485            } else {
486                if is_armed {
487                    // Use update() with params
488                    state_manager.update(Event::REQUEST_DISARM, params);
489                }
490            }
491        }
492    }
493
494    // -----------------------------------------------------------------
495    // Public Getters
496    // -----------------------------------------------------------------
497
498    /// Returns true if a new command has been processed
499    pub fn new_command(&mut self) -> bool {
500        if self.new_command {
501            self.new_command = false;
502            true
503        } else {
504            false
505        }
506    }
507
508    /// Gets the processed stick value (-1.0 to 1.0, or 0.0 to 1.0 for F)
509    pub fn stick(&self, channel: Stick) -> f32 {
510        self.stick_values[channel as usize]
511    }
512
513    /// Gets the processed switch value (true or false)
514    pub fn switch_on(&self, channel: Switch) -> bool {
515        self.switch_values[channel as usize]
516    }
517
518    /// Checks if a switch is mapped to a channel
519    pub fn switch_mapped(&self, channel: Switch) -> bool {
520        self.switches[channel as usize].mapped
521    }
522
523    /// Gets a reference to the raw, unprocessed RC data
524    pub fn get_rc_struct(&self) -> &RcStruct {
525        &self.rc
526    }
527
528    /// Reads a raw, normalized (0.0 to 1.0) channel value
529    pub fn read_raw_chan(&self, chan_idx: usize) -> f32 {
530        if chan_idx < self.rc.num_channels {
531            self.rc.chan[chan_idx]
532        } else {
533            0.0 // Safer than C++ OOB read
534        }
535    }
536}